diff --git a/.gitignore b/.gitignore index 19a4952ca..6f914744f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ *.idb # Kernel Module Compile Results -*.mod* *.cmd modules.order Module.symvers @@ -50,10 +49,6 @@ dkms.conf # Compiled Dynamic libraries -# Fortran module files -*.mod -*.smod - # Compiled Static libraries *.lai @@ -165,16 +160,6 @@ Temporary Items # Visual Studio 2015 database file *.VC.db -# Compiled Object files - -# Precompiled Headers - -# Compiled Dynamic libraries - -# Fortran module files - -# Compiled Static libraries - # Executables *.ipa @@ -547,10 +532,12 @@ NakamaBlueprintsTest/DerivedDataCache NakamaBlueprintsTest/Saved NakamaBlueprintsTest/Binaries -NakamaTest/Plugins/Nakama -NakamaTest/Intermediate -NakamaTest/Saved -NakamaTest/Binaries +IntegrationTests/Plugins/Nakama +IntegrationTests/Plugins/Satori +IntegrationTests/Intermediate +IntegrationTests/DerivedDataCache +IntegrationTests/Saved +IntegrationTests/Binaries Nakama/Intermediate Nakama/Saved @@ -563,3 +550,16 @@ Satori/Binaries vcpkg_installed/ /NakamaBlueprintsTest/Content/Developers /NakamaBlueprintsTest/Content/Collections +.env +proto/ +Reports/IntegrationTests/index.html +Reports/IntegrationTests/index.json +Reports/NakamaProfile/ +/IntegrationTests/Reports +NakamaTest/Binaries +NakamaTest/DerivedDataCache +NakamaTest/Intermediate/ +NakamaTest/Saved/ +Reports +IntegrationTests/satori/ +IntegrationTests/dashboards/ diff --git a/IntegrationTests/.editorconfig b/IntegrationTests/.editorconfig new file mode 100644 index 000000000..fbec8216a --- /dev/null +++ b/IntegrationTests/.editorconfig @@ -0,0 +1,91 @@ +[*.{cpp,h}] + +# Naming convention rules (note: currently need to be ordered from more to less specific) + +cpp_naming_rule.aactor_prefixed.symbols = aactor_class +cpp_naming_rule.aactor_prefixed.style = aactor_style + +cpp_naming_rule.swidget_prefixed.symbols = swidget_class +cpp_naming_rule.swidget_prefixed.style = swidget_style + +cpp_naming_rule.uobject_prefixed.symbols = uobject_class +cpp_naming_rule.uobject_prefixed.style = uobject_style + +cpp_naming_rule.booleans_prefixed.symbols = boolean_vars +cpp_naming_rule.booleans_prefixed.style = boolean_style + +cpp_naming_rule.structs_prefixed.symbols = structs +cpp_naming_rule.structs_prefixed.style = unreal_engine_structs + +cpp_naming_rule.enums_prefixed.symbols = enums +cpp_naming_rule.enums_prefixed.style = unreal_engine_enums + +cpp_naming_rule.templates_prefixed.symbols = templates +cpp_naming_rule.templates_prefixed.style = unreal_engine_templates + +cpp_naming_rule.general_names.symbols = all_symbols +cpp_naming_rule.general_names.style = unreal_engine_default + +# Naming convention symbols + +cpp_naming_symbols.aactor_class.applicable_kinds = class +cpp_naming_symbols.aactor_class.applicable_type = AActor + +cpp_naming_symbols.swidget_class.applicable_kinds = class +cpp_naming_symbols.swidget_class.applicable_type = SWidget + +cpp_naming_symbols.uobject_class.applicable_kinds = class +cpp_naming_symbols.uobject_class.applicable_type = UObject + +cpp_naming_symbols.boolean_vars.applicable_kinds = local,parameter,field +cpp_naming_symbols.boolean_vars.applicable_type = bool + +cpp_naming_symbols.enums.applicable_kinds = enum + +cpp_naming_symbols.templates.applicable_kinds = template_class + +cpp_naming_symbols.structs.applicable_kinds = struct + +cpp_naming_symbols.all_symbols.applicable_kinds = * + +# Naming convention styles + +cpp_naming_style.unreal_engine_default.capitalization = pascal_case +cpp_naming_style.unreal_engine_default.required_prefix = +cpp_naming_style.unreal_engine_default.required_suffix = +cpp_naming_style.unreal_engine_default.word_separator = + +cpp_naming_style.unreal_engine_enums.capitalization = pascal_case +cpp_naming_style.unreal_engine_enums.required_prefix = E +cpp_naming_style.unreal_engine_enums.required_suffix = +cpp_naming_style.unreal_engine_enums.word_separator = + +cpp_naming_style.unreal_engine_templates.capitalization = pascal_case +cpp_naming_style.unreal_engine_templates.required_prefix = T +cpp_naming_style.unreal_engine_templates.required_suffix = +cpp_naming_style.unreal_engine_templates.word_separator = + +cpp_naming_style.unreal_engine_structs.capitalization = pascal_case +cpp_naming_style.unreal_engine_structs.required_prefix = F +cpp_naming_style.unreal_engine_structs.required_suffix = +cpp_naming_style.unreal_engine_structs.word_separator = + +cpp_naming_style.uobject_style.capitalization = pascal_case +cpp_naming_style.uobject_style.required_prefix = U +cpp_naming_style.uobject_style.required_suffix = +cpp_naming_style.uobject_style.word_separator = + +cpp_naming_style.aactor_style.capitalization = pascal_case +cpp_naming_style.aactor_style.required_prefix = A +cpp_naming_style.aactor_style.required_suffix = +cpp_naming_style.aactor_style.word_separator = + +cpp_naming_style.swidget_style.capitalization = pascal_case +cpp_naming_style.swidget_style.required_prefix = S +cpp_naming_style.swidget_style.required_suffix = +cpp_naming_style.swidget_style.word_separator = + +cpp_naming_style.boolean_style.capitalization = pascal_case +cpp_naming_style.boolean_style.required_prefix = b +cpp_naming_style.boolean_style.required_suffix = +cpp_naming_style.boolean_style.word_separator = \ No newline at end of file diff --git a/IntegrationTests/.vsconfig b/IntegrationTests/.vsconfig new file mode 100644 index 000000000..c819cc29c --- /dev/null +++ b/IntegrationTests/.vsconfig @@ -0,0 +1,20 @@ +{ + "version": "1.0", + "components": [ + "Component.Unreal.Debugger", + "Component.Unreal.Ide", + "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.VC.14.38.17.8.ATL", + "Microsoft.VisualStudio.Component.VC.14.38.17.8.x86.x64", + "Microsoft.VisualStudio.Component.VC.14.44.17.14.ATL", + "Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64", + "Microsoft.VisualStudio.Component.VC.Llvm.Clang", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "Microsoft.VisualStudio.Component.Windows11SDK.22621", + "Microsoft.VisualStudio.Workload.CoreEditor", + "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Workload.NativeCrossPlat", + "Microsoft.VisualStudio.Workload.NativeDesktop", + "Microsoft.VisualStudio.Workload.NativeGame" + ] +} diff --git a/NakamaBlueprintsDemo/Config/DefaultEditor.ini b/IntegrationTests/Config/DefaultEditor.ini similarity index 100% rename from NakamaBlueprintsDemo/Config/DefaultEditor.ini rename to IntegrationTests/Config/DefaultEditor.ini diff --git a/NakamaTest/Config/DefaultEngine.ini b/IntegrationTests/Config/DefaultEngine.ini similarity index 86% rename from NakamaTest/Config/DefaultEngine.ini rename to IntegrationTests/Config/DefaultEngine.ini index 23ea44f6c..3a979df17 100644 --- a/NakamaTest/Config/DefaultEngine.ini +++ b/IntegrationTests/Config/DefaultEngine.ini @@ -2,8 +2,8 @@ [/Script/EngineSettings.GameMapsSettings] GameInstanceClass=/Script/Engine.GameInstance -EditorStartupMap=/Game/Maps/EmptyMap.EmptyMap -GameDefaultMap=/Game/Maps/EmptyMap.EmptyMap +EditorStartupMap=/Game/Maps/menu.menu +GameDefaultMap=/Game/Maps/menu.menu [/Script/HardwareTargeting.HardwareTargetingSettings] TargetedHardwareClass=Desktop @@ -12,9 +12,9 @@ DefaultGraphicsPerformance=Maximum AppliedDefaultGraphicsPerformance=Maximum [/Script/Engine.Engine] -+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/NakamaTest") -+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/NakamaTest") -+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="NakamaTestGameModeBase") ++ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/IntegrationTests") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/IntegrationTests") ++ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="IntegrationTestsGameModeBase") [/Script/Engine.NetworkSettings] n.VerifyPeer=False diff --git a/NakamaTest/Config/DefaultGame.ini b/IntegrationTests/Config/DefaultGame.ini similarity index 100% rename from NakamaTest/Config/DefaultGame.ini rename to IntegrationTests/Config/DefaultGame.ini diff --git a/NakamaBlueprintsDemo/Config/DefaultInput.ini b/IntegrationTests/Config/DefaultInput.ini similarity index 100% rename from NakamaBlueprintsDemo/Config/DefaultInput.ini rename to IntegrationTests/Config/DefaultInput.ini diff --git a/IntegrationTests/Content/Maps/EmptyMap.umap b/IntegrationTests/Content/Maps/EmptyMap.umap new file mode 100644 index 000000000..4aeb21b22 Binary files /dev/null and b/IntegrationTests/Content/Maps/EmptyMap.umap differ diff --git a/IntegrationTests/Content/Maps/menu.umap b/IntegrationTests/Content/Maps/menu.umap new file mode 100644 index 000000000..ecb170ea6 Binary files /dev/null and b/IntegrationTests/Content/Maps/menu.umap differ diff --git a/IntegrationTests/Content/Maps/testnakama.uasset b/IntegrationTests/Content/Maps/testnakama.uasset new file mode 100644 index 000000000..3ef9db3b6 Binary files /dev/null and b/IntegrationTests/Content/Maps/testnakama.uasset differ diff --git a/NakamaTest/NakamaTest.uproject b/IntegrationTests/IntegrationTests.uproject similarity index 50% rename from NakamaTest/NakamaTest.uproject rename to IntegrationTests/IntegrationTests.uproject index 97ec35541..6c2db2614 100644 --- a/NakamaTest/NakamaTest.uproject +++ b/IntegrationTests/IntegrationTests.uproject @@ -1,11 +1,11 @@ { "FileVersion": 3, - "EngineAssociation": "{EDE4B98B-4F6E-CD0F-7410-B3B99E43CB35}", + "EngineAssociation": "{C151EFA4-4AB5-6266-C2AA-BCA83C7CC033}", "Category": "", "Description": "", "Modules": [ { - "Name": "NakamaTest", + "Name": "IntegrationTests", "Type": "Runtime", "LoadingPhase": "Default" } @@ -15,6 +15,10 @@ "Name": "Nakama", "Enabled": true }, + { + "Name": "Satori", + "Enabled": true + }, { "Name": "AutomationDriverTests", "Enabled": true @@ -30,6 +34,18 @@ { "Name": "RuntimeTests", "Enabled": true + }, + { + "Name": "ModelViewViewModel", + "Enabled": true + }, + { + "Name": "VisualStudioTools", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64" + ], + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2" } ] -} \ No newline at end of file +} diff --git a/IntegrationTests/README.md b/IntegrationTests/README.md new file mode 100644 index 000000000..12d2626ff --- /dev/null +++ b/IntegrationTests/README.md @@ -0,0 +1,61 @@ +# Nakama Test Project +This is a test Unreal Engine project testing the core features of the Nakama Unreal Engine plugin. + +# Build and Test +Build the Nakama plugin and place it in the Plugins folder. + +Follow these instructions to package the example project: + +https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Packaging/ + +Follow these instructions to package the test project: + +https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Packaging/ + +### Windows + +To build the test, run: + +`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -targetconfig=Debug -project="$NAKAMA_UNREAL/IntegrationTests/IntegrationTests.uproject" -noP4 -installed -utf8output -build -cook -stage -package -verbose -stdout -nohostplatform -useshellexecute` + +To run the test, run: + +```bash +./IntegrationTests/Saved/StagedBuilds/Windows/IntegrationTests.exe -nullrhi -verbose -ExecCmds="Automation RunTests IntegrationTests.Core" +``` + +There appears to be a problem with logging to stdout on Windows, so you'll need to obtain the test logs from `IntegrationTests\Saved\StagedBuilds\Windows\IntegrationTests\Saved\Logs\IntegrationTests.log`. + +### Mac + +To build the test, run: + +`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project="$NAKAMA_UNREAL/IntegrationTests/IntegrationTests.uproject" -targetConfig=Debug -noP4 -platform=Mac -Architecture_Mac=arm64 -targetconfig=Debug -installed -unrealexe=UnrealEditor -utf8output -build -cook -stage -package -verbose` + +To run the test, run: + +`./IntegrationTests/Binaries/Mac/IntegrationTests.app/Contents/MacOS/IntegrationTests -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests IntegrationTests.Core"` + +You can pass `List` instead to the `Automation` command to view all tests. It will include engine tests. + +You can also pass `-LogCmds=" verbose"` where `` is one of those defined by `DEFINE_LOG_CATEGORY`. + +### Linux + +To build the test, run: + +`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project="$NAKAMA_UNREAL/IntegrationTests/IntegrationTests.uproject" -clientconfig=Test -noP4 -platform=Linux -targetconfig=Debug -installed -utf8output -build -cook -stage -package -verbose` + +To run the test: + +`IntegrationTests/Binaries/Linux/IntegrationTests-Linux-Test -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests IntegrationTests.Core"` + +To debug with LLDB on Mac: + +`lldb ${NAKAMA_UNREAL}/IntegrationTests/Binaries/Mac/IntegrationTests-Mac-Test.app/Contents/MacOS/IntegrationTests-Mac-Test` + +Set the startup args inside the lldb shell: + +`settings set -- target.run-args -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests IntegrationTests.Core"` + +Then call `run`. diff --git a/IntegrationTests/Source/IntegrationTests.Target.cs b/IntegrationTests/Source/IntegrationTests.Target.cs new file mode 100644 index 000000000..205ce53ce --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests.Target.cs @@ -0,0 +1,14 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class IntegrationTestsTarget : TargetRules +{ + public IntegrationTestsTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + ExtraModuleNames.AddRange( new string[] { "IntegrationTests" } ); + bUseLoggingInShipping = true; + } +} diff --git a/IntegrationTests/Source/IntegrationTests/IntegrationTests.Build.cs b/IntegrationTests/Source/IntegrationTests/IntegrationTests.Build.cs new file mode 100644 index 000000000..1b2b3b602 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/IntegrationTests.Build.cs @@ -0,0 +1,14 @@ +// Copyright 2023 The Nakama Authors. + +using System; +using System.IO; +using UnrealBuildTool; + +public class IntegrationTests : ModuleRules +{ + public IntegrationTests(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + PrivateDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "FieldNotification", "InputCore", "Json", "HTTP", "NakamaApi", "Nakama", "NakamaBlueprints", "SatoriApi", "Satori", "SatoriBlueprints", "WebSockets", "Slate", "SlateCore", "UMG", "ModelViewViewModel" }); + } +} diff --git a/IntegrationTests/Source/IntegrationTests/IntegrationTests.cpp b/IntegrationTests/Source/IntegrationTests/IntegrationTests.cpp new file mode 100644 index 000000000..8223ffe6d --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/IntegrationTests.cpp @@ -0,0 +1,6 @@ +// Copyright 2023 The Nakama Authors. + +#include "IntegrationTests.h" +#include "CoreMinimal.h" + +IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, IntegrationTests, "IntegrationTests"); diff --git a/NakamaTest/Source/NakamaTest/NakamaTest.h b/IntegrationTests/Source/IntegrationTests/IntegrationTests.h similarity index 81% rename from NakamaTest/Source/NakamaTest/NakamaTest.h rename to IntegrationTests/Source/IntegrationTests/IntegrationTests.h index 824215b3d..8371bbbbd 100644 --- a/NakamaTest/Source/NakamaTest/NakamaTest.h +++ b/IntegrationTests/Source/IntegrationTests/IntegrationTests.h @@ -4,7 +4,7 @@ #include "CoreMinimal.h" -class NakamaTest : public IModuleInterface +class IntegrationTests : public IModuleInterface { public: diff --git a/IntegrationTests/Source/IntegrationTests/Tests/FutureThreadingTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/FutureThreadingTests.cpp new file mode 100644 index 000000000..c6bf82eef --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/FutureThreadingTests.cpp @@ -0,0 +1,121 @@ +/** + * Future Threading Tests + * + * Demonstrates bug: TSatoriFuture terminal .Next() callbacks ran on the UE + * background task thread pool instead of the game thread. + * + * Root cause (Satori.h, old TSatoriFuture:169-179): the terminal Next() overload + * passed the user callback directly to UE::Tasks::Launch without any + * AsyncTask(ENamedThreads::GameThread, ...) dispatch. Callers that touched + * UObject*, fired delegates, or updated UI would therefore hit non-thread-safe + * access and intermittent crashes. + * + * Fix: both TNakamaFuture and TSatoriFuture are now aliases for the shared + * TAsyncFuture<> template (AsyncFuture.h / NakamaApi module), which dispatches + * every user-visible callback — chaining and terminal — to the game thread. + * + * The Satori test below verifies the fix. The Nakama control test was already + * passing before the consolidation. + */ + +#include +#include "Misc/AutomationTest.h" +#include "AsyncFuture.h" + +// ============================================================================ +// Satori: terminal .Next() must run on the game thread +// ============================================================================ + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FSatoriTerminalNextOnGameThreadTest, + "IntegrationTests.Threading.Satori.TerminalNextMustRunOnGameThread", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter +) + +struct FSatoriThreadingTestResult +{ + bool bIsError = false; +}; + +bool FSatoriTerminalNextOnGameThreadTest::RunTest(const FString& Parameters) +{ + TSharedRef> bCallbackRanOnGameThread = MakeShared>(false); + TSharedRef> bCallbackInvoked = MakeShared>(false); + + MakeCompletedAsyncFuture(FSatoriThreadingTestResult{}).Next( + [bCallbackRanOnGameThread, bCallbackInvoked](FSatoriThreadingTestResult) + { + bCallbackRanOnGameThread->store(IsInGameThread()); + bCallbackInvoked->store(true); + }); + + // Spin-wait up to 5 s, pumping the game-thread task queue each iteration + // so the AsyncTask dispatch can actually execute. + constexpr double TimeoutSeconds = 5.0; + const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds; + while (!bCallbackInvoked->load()) + { + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + if (FPlatformTime::Seconds() > Deadline) + { + AddError(TEXT("Timed out waiting for TSatoriFuture terminal .Next() callback")); + return false; + } + FPlatformProcess::Sleep(0.01f); + } + + TestTrue( + TEXT("TSatoriFuture terminal .Next() callback must run on the game thread"), + bCallbackRanOnGameThread->load() + ); + + return true; +} + +// ============================================================================ +// Nakama: same guarantee, kept as a control / regression test +// ============================================================================ + +struct FNakamaThreadingTestResult +{ + bool bIsError = false; +}; + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FNakamaTerminalNextOnGameThreadTest, + "IntegrationTests.Threading.Nakama.TerminalNextMustRunOnGameThread", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter +) + +bool FNakamaTerminalNextOnGameThreadTest::RunTest(const FString& Parameters) +{ + TSharedRef> bCallbackRanOnGameThread = MakeShared>(false); + TSharedRef> bCallbackInvoked = MakeShared>(false); + + MakeCompletedAsyncFuture(FNakamaThreadingTestResult{}).Next( + [bCallbackRanOnGameThread, bCallbackInvoked](FNakamaThreadingTestResult) + { + bCallbackRanOnGameThread->store(IsInGameThread()); + bCallbackInvoked->store(true); + }); + + constexpr double TimeoutSeconds = 5.0; + const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds; + while (!bCallbackInvoked->load()) + { + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + if (FPlatformTime::Seconds() > Deadline) + { + AddError(TEXT("Timed out waiting for TNakamaFuture terminal .Next() callback")); + return false; + } + FPlatformProcess::Sleep(0.01f); + } + + TestTrue( + TEXT("TNakamaFuture terminal .Next() callback must run on the game thread"), + bCallbackRanOnGameThread->load() + ); + + return true; +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/NakamaBlueprintTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/NakamaBlueprintTests.cpp new file mode 100644 index 000000000..5945a5f77 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/NakamaBlueprintTests.cpp @@ -0,0 +1,2218 @@ +/** + * Blueprint Async Action Test Suite + * + * Tests for NakamaBlueprints module UBlueprintAsyncActionBase subclasses. + * Each test fires a BP async action, then verifies the server-side effect + * through the C++ client (which supports lambda callbacks natively). + * + * This avoids creating any UObject helpers for delegate binding. + */ + +#include "Misc/AutomationTest.h" +#include "NakamaApi.h" +#include "NakamaClientBlueprintLibrary.h" +#include "Misc/Guid.h" +#include "Containers/Ticker.h" + +// Helper: keep the BP action alive for the duration of the async call. Tests pass +// GetTransientPackage() as WorldContextObject — this satisfies UE 5.7's requirement +// that NewObject<> receives a valid packaged outer, while RegisterWithGameInstance +// remains effectively a no-op (transient package has no GameInstance). +// The base class constructor sets RF_StrongRefOnFrame, and SetReadyToDestroy() +// clears it. We AddToRoot() to prevent GC across frames, then poll until +// RF_StrongRefOnFrame is cleared (signalling the action called SetReadyToDestroy). +static void VerifyWhenComplete(UBlueprintAsyncActionBase* Action, TFunction VerifyFunc) +{ + Action->AddToRoot(); + + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([Action, VerifyFunc = MoveTemp(VerifyFunc)](float) -> bool + { + if (Action->HasAnyFlags(RF_StrongRefOnFrame)) + { + return true; // keep ticking — action hasn't called SetReadyToDestroy yet + } + Action->RemoveFromRoot(); + VerifyFunc(); + return false; + }), + 0.0f + ); +} + +// ============================================================================ +// HEALTHCHECK TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPHealthcheckSpec, "IntegrationTests.NakamaBlueprint.Healthcheck", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaSession Session; + FNakamaClientConfig Client; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPHealthcheckSpec) + +const FString FNakamaBPHealthcheckSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPHealthcheckSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPHealthcheckSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("Healthcheck", [this]() + { + LatentIt("should pass healthcheck", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientHealthcheck::Healthcheck(GetTransientPackage(), Client, Session); + Action->Activate(); + + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::Healthcheck(Client, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// AUTHENTICATION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPAuthSpec, "IntegrationTests.NakamaBlueprint.Auth", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaBPAuthSpec) + +const FString FNakamaBPAuthSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPAuthSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPAuthSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + Session = FNakamaSession(); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) + { + Done.Execute(); + return; + } + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("AuthenticateCustom", [this]() + { + LatentIt("should authenticate with custom ID", [this](const FDoneDelegate& Done) + { + FString CustomId = GenerateId(); + + auto* Action = UNakamaClientAuthenticateCustom::AuthenticateCustom( + GetTransientPackage(), Client, CustomId, true, TEXT(""), {}); + Action->Activate(); + + // Verify: re-auth with create=false should succeed (account was created) + VerifyWhenComplete(Action, [this, Done, CustomId]() + { + FNakamaAccountCustom Account; + Account.Id = CustomId; + NakamaApi::AuthenticateCustom(Client, Account, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + TestTrue("Session has token", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Re-auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("AuthenticateEmail", [this]() + { + LatentIt("should authenticate with email", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("test_%s@example.com"), *GenerateShortId()); + FString Password = TEXT("password123!"); + + auto* Action = UNakamaClientAuthenticateEmail::AuthenticateEmail( + GetTransientPackage(), Client, Email, Password, true, TEXT(""), {}); + Action->Activate(); + + // Verify: re-auth with create=false should succeed + VerifyWhenComplete(Action, [this, Done, Email, Password]() + { + FNakamaAccountEmail Account; + Account.Email = Email; + Account.Password = Password; + NakamaApi::AuthenticateEmail(Client, Account, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + TestTrue("Session has token", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Re-auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("AuthenticateDevice", [this]() + { + LatentIt("should authenticate with device ID", [this](const FDoneDelegate& Done) + { + FString DeviceId = GenerateId(); + auto* Action = UNakamaClientAuthenticateDevice::AuthenticateDevice( + GetTransientPackage(), Client, DeviceId, true, TEXT(""), {}); + Action->Activate(); + + // Verify: re-auth with create=false should succeed + VerifyWhenComplete(Action, [this, Done, DeviceId]() + { + FNakamaAccountDevice Account; + Account.Id = DeviceId; + NakamaApi::AuthenticateDevice(Client, Account, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + TestTrue("Session has token", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Re-auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// SESSION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPSessionSpec, "IntegrationTests.NakamaBlueprint.Session", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPSessionSpec) + +const FString FNakamaBPSessionSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPSessionSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPSessionSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("SessionRefresh", [this]() + { + LatentIt("should refresh session", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientSessionRefresh::SessionRefresh( + GetTransientPackage(), Client, Session.RefreshToken, TMap()); + Action->Activate(); + + // Verify: original session token still works for API call + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& Result) + { + TestTrue("Account retrieved", !Result.User.Id.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("SessionLogout", [this]() + { + LatentIt("should logout session", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientSessionLogout::SessionLogout( + GetTransientPackage(), Client, Session, Session.Token, Session.RefreshToken); + Action->Activate(); + + // Verify: action completed without crash; server is still responsive + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::Healthcheck(Client, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// ACCOUNT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPAccountSpec, "IntegrationTests.NakamaBlueprint.Account", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + FString UserId; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPAccountSpec) + +const FString FNakamaBPAccountSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPAccountSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPAccountSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("GetAccount", [this]() + { + LatentIt("should get account", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientGetAccount::GetAccount(GetTransientPackage(), Client, Session); + Action->Activate(); + + // Verify via C++ GetAccount + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& Result) + { + TestEqual("User ID matches", Result.User.Id, UserId); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UpdateAccount", [this]() + { + LatentIt("should update and verify display name", [this](const FDoneDelegate& Done) + { + FString NewDisplayName = TEXT("BPTestUser"); + + auto* Action = UNakamaClientUpdateAccount::UpdateAccount( + GetTransientPackage(), Client, Session, + TEXT(""), NewDisplayName, TEXT(""), TEXT(""), TEXT(""), TEXT("")); + Action->Activate(); + + // Verify via C++ GetAccount + VerifyWhenComplete(Action, [this, Done, NewDisplayName]() + { + NakamaApi::GetAccount(Client, Session, + [this, Done, NewDisplayName](const FNakamaAccount& Result) + { + TestEqual("Display name updated", Result.User.DisplayName, NewDisplayName); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("GetUsers", [this]() + { + LatentIt("should get users by ID", [this](const FDoneDelegate& Done) + { + TArray Ids = { UserId }; + TArray Usernames; + TArray FacebookIds; + + auto* Action = UNakamaClientGetUsers::GetUsers( + GetTransientPackage(), Client, Session, Ids, Usernames, FacebookIds); + Action->Activate(); + + // Verify via C++ GetUsers + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::GetUsers(Client, Session, { UserId }, {}, {}, + [this, Done](const FNakamaUsers& Result) + { + TestTrue("Got at least one user", Result.Users.Num() > 0); + if (Result.Users.Num() > 0) + { + TestEqual("User ID matches", Result.Users[0].Id, UserId); + } + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// FRIENDS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPFriendsSpec, "IntegrationTests.NakamaBlueprint.Friends", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + FString UserId; + + FNakamaClientConfig FriendClient; + FNakamaSession FriendSession; + FString FriendUserId; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPFriendsSpec) + +const FString FNakamaBPFriendsSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPFriendsSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPFriendsSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + + FriendClient = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + + FNakamaAccountCustom FriendAccount; + FriendAccount.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(FriendClient, FriendAccount, true, TEXT(""), + [this, Done](const FNakamaSession& FResult) + { + FriendSession = FResult; + NakamaApi::GetAccount(FriendClient, FriendSession, + [this, Done](const FNakamaAccount& FAccResult) + { + FriendUserId = FAccResult.User.Id; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Friend GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Friend auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [this, Done]() + { + NakamaApi::DeleteAccount(Client, FriendSession, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }, + [this, Done](const FNakamaError&) + { + NakamaApi::DeleteAccount(Client, FriendSession, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + } + ); + }); + + Describe("AddFriends", [this]() + { + LatentIt("should add a friend and verify via list", [this](const FDoneDelegate& Done) + { + TArray Ids = { FriendUserId }; + TArray Usernames; + + auto* Action = UNakamaClientAddFriends::AddFriends( + GetTransientPackage(), Client, Session, Ids, Usernames, TEXT("")); + Action->Activate(); + + // Verify via C++ ListFriends + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListFriends(Client, Session, 10, 0, TEXT(""), + [this, Done](const FNakamaFriendList& Result) + { + TestTrue("Friend list is not empty", Result.Friends.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListFriends failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("ListFriends", [this]() + { + LatentIt("should list friends after adding one", [this](const FDoneDelegate& Done) + { + // Setup: add friend via C++ + NakamaApi::AddFriends(Client, Session, { FriendUserId }, {}, TEXT(""), + [this, Done]() + { + // Fire BP ListFriends + auto* Action = UNakamaClientListFriends::ListFriends( + GetTransientPackage(), Client, Session, 10, 0, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListFriends(Client, Session, 10, 0, TEXT(""), + [this, Done](const FNakamaFriendList& Result) + { + TestTrue("Friend list has entries", Result.Friends.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListFriends failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("AddFriends setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("BlockFriends", [this]() + { + LatentIt("should block a friend", [this](const FDoneDelegate& Done) + { + TArray Ids = { FriendUserId }; + TArray Usernames; + + auto* Action = UNakamaClientBlockFriends::BlockFriends( + GetTransientPackage(), Client, Session, Ids, Usernames); + Action->Activate(); + + // Verify: blocked friend appears in friend list with state=3 (blocked) + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListFriends(Client, Session, 10, 3, TEXT(""), + [this, Done](const FNakamaFriendList& Result) + { + TestTrue("Blocked friend list has entries", Result.Friends.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListFriends failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("DeleteFriends", [this]() + { + LatentIt("should delete a friend", [this](const FDoneDelegate& Done) + { + // Setup: add friend via C++ + NakamaApi::AddFriends(Client, Session, { FriendUserId }, {}, TEXT(""), + [this, Done]() + { + // Fire BP DeleteFriends + TArray Ids = { FriendUserId }; + TArray Usernames; + + auto* Action = UNakamaClientDeleteFriends::DeleteFriends( + GetTransientPackage(), Client, Session, Ids, Usernames); + Action->Activate(); + + // Verify: friend list is now empty + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListFriends(Client, Session, 10, 0, TEXT(""), + [this, Done](const FNakamaFriendList& Result) + { + TestEqual("Friend list is empty", Result.Friends.Num(), 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListFriends failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("AddFriends setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); +} + +// ============================================================================ +// GROUPS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPGroupsSpec, "IntegrationTests.NakamaBlueprint.Groups", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + FString UserId; + + + FNakamaSession Session2; + FString UserId2; + + FString GroupId; + FString GroupName; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaBPGroupsSpec) + +const FString FNakamaBPGroupsSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPGroupsSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPGroupsSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account1; + Account1.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account1, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account2, true, TEXT(""), + [this, Done](const FNakamaSession& Result2) + { + Session2 = Result2; + NakamaApi::GetAccount(Client, Session2, + [this, Done](const FNakamaAccount& AccResult2) + { + UserId2 = AccResult2.User.Id; + + // Create a group as user1 + GroupName = FString::Printf(TEXT("bp_grp_%s"), *GenerateShortId()); + NakamaApi::CreateGroup(Client, Session, GroupName, TEXT("Test group"), + TEXT("en"), TEXT(""), true, 100, + [this, Done](const FNakamaGroup& Group) + { + GroupId = Group.Id; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("CreateGroup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount2 failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Auth2 failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteGroup(Client, Session, GroupId, + [this, Done]() + { + NakamaApi::DeleteAccount(Client, Session, + [this, Done]() + { + NakamaApi::DeleteAccount(Client, Session2, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }, + [this, Done](const FNakamaError&) + { + NakamaApi::DeleteAccount(Client, Session2, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + } + ); + }, + [this, Done](const FNakamaError&) + { + NakamaApi::DeleteAccount(Client, Session, + [this, Done]() + { + NakamaApi::DeleteAccount(Client, Session2, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }, + [this, Done](const FNakamaError&) + { + NakamaApi::DeleteAccount(Client, Session2, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + } + ); + } + ); + }); + + Describe("CreateGroup", [this]() + { + LatentIt("should create a group", [this](const FDoneDelegate& Done) + { + FString NewGroupName = FString::Printf(TEXT("bp_new_%s"), *GenerateShortId()); + + auto* Action = UNakamaClientCreateGroup::CreateGroup( + GetTransientPackage(), Client, Session, + NewGroupName, TEXT("Test group"), TEXT("en"), TEXT(""), true, 100); + Action->Activate(); + + // Verify via C++ ListUserGroups (ListGroups always sends open filter which conflicts with name) + VerifyWhenComplete(Action, [this, Done, NewGroupName]() + { + NakamaApi::ListUserGroups(Client, Session, UserId, 100, 0, TEXT(""), + [this, Done, NewGroupName](const FNakamaUserGroupList& Result) + { + FString NewGroupId; + bool Found = false; + for (const auto& UG : Result.UserGroups) + { + if (UG.Group.Name == NewGroupName) + { + Found = true; + NewGroupId = UG.Group.Id; + break; + } + } + TestTrue("Created group found in user groups", Found); + if (!NewGroupId.IsEmpty()) + { + NakamaApi::DeleteGroup(Client, Session, NewGroupId, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + } + else + { + Done.Execute(); + } + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListUserGroups failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UpdateGroup", [this]() + { + LatentIt("should update a group", [this](const FDoneDelegate& Done) + { + FString NewDesc = TEXT("Updated description"); + + auto* Action = UNakamaClientUpdateGroup::UpdateGroup( + GetTransientPackage(), Client, Session, + GroupId, GroupName, NewDesc, TEXT("en"), TEXT(""), true); + Action->Activate(); + + // Verify: group still exists and is accessible + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + TestTrue("Group still has users after update", Result.GroupUsers.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("DeleteGroup", [this]() + { + LatentIt("should delete a group", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientDeleteGroup::DeleteGroup( + GetTransientPackage(), Client, Session, GroupId); + Action->Activate(); + + // Verify: group no longer in user's groups + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListUserGroups(Client, Session, UserId, 100, 0, TEXT(""), + [this, Done](const FNakamaUserGroupList& Result) + { + bool Found = false; + for (const auto& UG : Result.UserGroups) + { + if (UG.Group.Id == GroupId) + { + Found = true; + break; + } + } + TestFalse("Deleted group not in user groups", Found); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListUserGroups failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("ListGroups", [this]() + { + LatentIt("should list groups", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientListGroups::ListGroups( + GetTransientPackage(), Client, Session, + TEXT(""), TEXT(""), 100, TEXT(""), 0, true); + Action->Activate(); + + // Verify via C++ ListGroups + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroups(Client, Session, TEXT(""), TEXT(""), 100, TEXT(""), 0, true, + [this, Done](const FNakamaGroupList& Result) + { + TestTrue("Group list has entries", Result.Groups.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroups failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("JoinGroup", [this]() + { + LatentIt("should join a group", [this](const FDoneDelegate& Done) + { + // User2 fires BP JoinGroup on user1's open group + auto* Action = UNakamaClientJoinGroup::JoinGroup( + GetTransientPackage(), Client, Session2, GroupId); + Action->Activate(); + + // Verify via C++ ListGroupUsers + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + TestTrue("Group has at least 2 users", Result.GroupUsers.Num() >= 2); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("AddGroupUsers", [this]() + { + LatentIt("should add users to a group", [this](const FDoneDelegate& Done) + { + TArray UserIds = { UserId2 }; + + auto* Action = UNakamaClientAddGroupUsers::AddGroupUsers( + GetTransientPackage(), Client, Session, GroupId, UserIds); + Action->Activate(); + + // Verify via C++ ListGroupUsers + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + TestTrue("Group has at least 2 users", Result.GroupUsers.Num() >= 2); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("KickGroupUsers", [this]() + { + LatentIt("should kick users from a group", [this](const FDoneDelegate& Done) + { + // Setup: add user2 to group via C++ + NakamaApi::AddGroupUsers(Client, Session, GroupId, { UserId2 }, + [this, Done]() + { + // Fire BP KickGroupUsers + TArray UserIds = { UserId2 }; + + auto* Action = UNakamaClientKickGroupUsers::KickGroupUsers( + GetTransientPackage(), Client, Session, GroupId, UserIds); + Action->Activate(); + + // Verify user2 is no longer in group + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + bool Found = false; + for (const auto& GU : Result.GroupUsers) + { + if (GU.User.Id == UserId2) + { + Found = true; + break; + } + } + TestFalse("Kicked user not in group", Found); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("AddGroupUsers setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("PromoteGroupUsers", [this]() + { + LatentIt("should promote users in a group", [this](const FDoneDelegate& Done) + { + // Setup: add user2 to group via C++ + NakamaApi::AddGroupUsers(Client, Session, GroupId, { UserId2 }, + [this, Done]() + { + // Fire BP PromoteGroupUsers + TArray UserIds = { UserId2 }; + + auto* Action = UNakamaClientPromoteGroupUsers::PromoteGroupUsers( + GetTransientPackage(), Client, Session, GroupId, UserIds); + Action->Activate(); + + // Verify: user2 is still in group (promotion doesn't remove) + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + bool Found = false; + for (const auto& GU : Result.GroupUsers) + { + if (GU.User.Id == UserId2) + { + Found = true; + break; + } + } + TestTrue("Promoted user still in group", Found); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("AddGroupUsers setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("LeaveGroup", [this]() + { + LatentIt("should leave a group", [this](const FDoneDelegate& Done) + { + // Setup: user2 joins group via C++ + NakamaApi::JoinGroup(Client, Session2, GroupId, + [this, Done]() + { + // Fire BP LeaveGroup as user2 + auto* Action = UNakamaClientLeaveGroup::LeaveGroup( + GetTransientPackage(), Client, Session2, GroupId); + Action->Activate(); + + // Verify user2 no longer in group + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + bool Found = false; + for (const auto& GU : Result.GroupUsers) + { + if (GU.User.Id == UserId2) + { + Found = true; + break; + } + } + TestFalse("User2 left the group", Found); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("JoinGroup setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("ListGroupUsers", [this]() + { + LatentIt("should list group users", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientListGroupUsers::ListGroupUsers( + GetTransientPackage(), Client, Session, GroupId, 10, 0, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListGroupUsers(Client, Session, GroupId, 10, 0, TEXT(""), + [this, Done](const FNakamaGroupUserList& Result) + { + TestTrue("Group has at least one user (owner)", Result.GroupUsers.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListGroupUsers failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("ListUserGroups", [this]() + { + LatentIt("should list user groups", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientListUserGroups::ListUserGroups( + GetTransientPackage(), Client, Session, UserId, 10, 0, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListUserGroups(Client, Session, UserId, 10, 0, TEXT(""), + [this, Done](const FNakamaUserGroupList& Result) + { + TestTrue("User has at least one group", Result.UserGroups.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListUserGroups failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// STORAGE TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPStorageSpec, "IntegrationTests.NakamaBlueprint.Storage", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + FString UserId; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPStorageSpec) + +const FString FNakamaBPStorageSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPStorageSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPStorageSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + NakamaApi::GetAccount(Client, Session, + [this, Done](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("GetAccount failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("WriteStorageObjects", [this]() + { + LatentIt("should write and read back storage objects", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("key_%s"), *GenerateId()); + + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("bp_test"); + Obj.Key = Key; + Obj.Value = TEXT("{\"score\":42}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + + TArray Objects = { Obj }; + + auto* Action = UNakamaClientWriteStorageObjects::WriteStorageObjects( + GetTransientPackage(), Client, Session, Objects); + Action->Activate(); + + // Verify via C++ ReadStorageObjects + VerifyWhenComplete(Action, [this, Done, Key]() + { + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("bp_test"); + ReadId.Key = Key; + ReadId.UserId = UserId; + + NakamaApi::ReadStorageObjects(Client, Session, { ReadId }, + [this, Done](const FNakamaStorageObjects& Result) + { + TestTrue("Got at least one object", Result.Objects.Num() > 0); + if (Result.Objects.Num() > 0) + { + TestTrue("Value contains score", Result.Objects[0].Value.Contains(TEXT("42"))); + } + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ReadStorage failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + LatentIt("should write then verify value content", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("key_%s"), *GenerateId()); + + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("bp_test"); + Obj.Key = Key; + Obj.Value = TEXT("{\"level\":99,\"name\":\"hero\"}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + + TArray Objects = { Obj }; + + auto* Action = UNakamaClientWriteStorageObjects::WriteStorageObjects( + GetTransientPackage(), Client, Session, Objects); + Action->Activate(); + + // Verify via C++ ReadStorageObjects + VerifyWhenComplete(Action, [this, Done, Key]() + { + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("bp_test"); + ReadId.Key = Key; + ReadId.UserId = UserId; + + NakamaApi::ReadStorageObjects(Client, Session, { ReadId }, + [this, Done](const FNakamaStorageObjects& Result) + { + TestTrue("Got at least one object", Result.Objects.Num() > 0); + if (Result.Objects.Num() > 0) + { + TestTrue("Value contains level", Result.Objects[0].Value.Contains(TEXT("99"))); + TestTrue("Value contains name", Result.Objects[0].Value.Contains(TEXT("hero"))); + } + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ReadStorage failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("ReadStorageObjects", [this]() + { + LatentIt("should read storage objects", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("key_%s"), *GenerateId()); + + // Setup: write via C++ + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("bp_test"); + Obj.Key = Key; + Obj.Value = TEXT("{\"data\":true}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + + NakamaApi::WriteStorageObjects(Client, Session, { Obj }, + [this, Done, Key](const FNakamaStorageObjectAcks& Acks) + { + // Fire BP ReadStorageObjects + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("bp_test"); + ReadId.Key = Key; + ReadId.UserId = UserId; + + auto* Action = UNakamaClientReadStorageObjects::ReadStorageObjects( + GetTransientPackage(), Client, Session, { ReadId }); + Action->Activate(); + + // Verify via C++ that object exists + VerifyWhenComplete(Action, [this, Done, Key]() + { + FNakamaReadStorageObjectId VerifyId; + VerifyId.Collection = TEXT("bp_test"); + VerifyId.Key = Key; + VerifyId.UserId = UserId; + + NakamaApi::ReadStorageObjects(Client, Session, { VerifyId }, + [this, Done](const FNakamaStorageObjects& Result) + { + TestTrue("Object still readable", Result.Objects.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ReadStorage failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("WriteStorage setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("ListStorageObjects", [this]() + { + LatentIt("should list storage objects", [this](const FDoneDelegate& Done) + { + // Setup: write an object via C++ + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("bp_list_test"); + Obj.Key = FString::Printf(TEXT("key_%s"), *GenerateId()); + Obj.Value = TEXT("{\"x\":1}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + + NakamaApi::WriteStorageObjects(Client, Session, { Obj }, + [this, Done](const FNakamaStorageObjectAcks& Acks) + { + // Fire BP ListStorageObjects + auto* Action = UNakamaClientListStorageObjects::ListStorageObjects( + GetTransientPackage(), Client, Session, UserId, TEXT("bp_list_test"), 10, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListStorageObjects(Client, Session, UserId, TEXT("bp_list_test"), 10, TEXT(""), + [this, Done](const FNakamaStorageObjectList& Result) + { + TestTrue("Listed at least one object", Result.Objects.Num() > 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListStorage failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("WriteStorage setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("DeleteStorageObjects", [this]() + { + LatentIt("should delete storage objects", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("key_%s"), *GenerateId()); + + // Setup: write an object via C++ + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("bp_del_test"); + Obj.Key = Key; + Obj.Value = TEXT("{\"del\":true}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + + NakamaApi::WriteStorageObjects(Client, Session, { Obj }, + [this, Done, Key](const FNakamaStorageObjectAcks& Acks) + { + // Fire BP DeleteStorageObjects + FNakamaDeleteStorageObjectId DelId; + DelId.Collection = TEXT("bp_del_test"); + DelId.Key = Key; + DelId.Version = Acks.Acks.Num() > 0 ? Acks.Acks[0].Version : TEXT(""); + + auto* Action = UNakamaClientDeleteStorageObjects::DeleteStorageObjects( + GetTransientPackage(), Client, Session, { DelId }); + Action->Activate(); + + // Verify: object is gone + VerifyWhenComplete(Action, [this, Done, Key]() + { + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("bp_del_test"); + ReadId.Key = Key; + ReadId.UserId = UserId; + + NakamaApi::ReadStorageObjects(Client, Session, { ReadId }, + [this, Done](const FNakamaStorageObjects& Result) + { + TestEqual("Object deleted", Result.Objects.Num(), 0); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ReadStorage failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("WriteStorage setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); +} + +// ============================================================================ +// LINK / UNLINK TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPLinkSpec, "IntegrationTests.NakamaBlueprint.Link", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaBPLinkSpec) + +const FString FNakamaBPLinkSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPLinkSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPLinkSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("LinkCustom", [this]() + { + LatentIt("should link a custom ID", [this](const FDoneDelegate& Done) + { + FString NewId = GenerateId(); + + auto* Action = UNakamaClientLinkCustom::LinkCustom( + GetTransientPackage(), Client, Session, NewId, TMap()); + Action->Activate(); + + // Verify: can authenticate with the linked ID + VerifyWhenComplete(Action, [this, Done, NewId]() + { + FNakamaAccountCustom LinkedAccount; + LinkedAccount.Id = NewId; + + NakamaApi::AuthenticateCustom(Client, LinkedAccount, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + TestTrue("Linked custom auth succeeded", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Linked auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UnlinkCustom", [this]() + { + LatentIt("should unlink a custom ID", [this](const FDoneDelegate& Done) + { + FString ExtraId = GenerateId(); + FString DeviceId = GenerateId(); + + // Setup: link a device first (so there's a fallback auth method), + // then link an extra custom ID (replaces original) + NakamaApi::LinkDevice(Client, Session, DeviceId, TMap(), + [this, Done, ExtraId]() + { + NakamaApi::LinkCustom(Client, Session, ExtraId, TMap(), + [this, Done, ExtraId]() + { + // Fire BP UnlinkCustom + auto* Action = UNakamaClientUnlinkCustom::UnlinkCustom( + GetTransientPackage(), Client, Session, ExtraId, TMap()); + Action->Activate(); + + // Verify: can no longer auth with the unlinked ID + VerifyWhenComplete(Action, [this, Done, ExtraId]() + { + FNakamaAccountCustom UnlinkedAccount; + UnlinkedAccount.Id = ExtraId; + + NakamaApi::AuthenticateCustom(Client, UnlinkedAccount, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + AddError(TEXT("Auth should have failed for unlinked ID")); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + // Expected: auth fails because ID is unlinked + TestTrue("Unlinked ID auth rejected", true); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("LinkCustom setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("LinkDevice setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("LinkDevice", [this]() + { + LatentIt("should link a device ID", [this](const FDoneDelegate& Done) + { + FString DeviceId = GenerateId(); + + auto* Action = UNakamaClientLinkDevice::LinkDevice( + GetTransientPackage(), Client, Session, DeviceId, TMap()); + Action->Activate(); + + // Verify: can authenticate with the linked device ID + VerifyWhenComplete(Action, [this, Done, DeviceId]() + { + FNakamaAccountDevice LinkedDevice; + LinkedDevice.Id = DeviceId; + + NakamaApi::AuthenticateDevice(Client, LinkedDevice, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + TestTrue("Linked device auth succeeded", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Linked device auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UnlinkDevice", [this]() + { + LatentIt("should unlink a device ID", [this](const FDoneDelegate& Done) + { + FString DeviceId = GenerateId(); + + // Setup: link device via C++ + NakamaApi::LinkDevice(Client, Session, DeviceId, TMap(), + [this, Done, DeviceId]() + { + // Fire BP UnlinkDevice + auto* Action = UNakamaClientUnlinkDevice::UnlinkDevice( + GetTransientPackage(), Client, Session, DeviceId, TMap()); + Action->Activate(); + + // Verify: can no longer auth with the unlinked device + VerifyWhenComplete(Action, [this, Done, DeviceId]() + { + FNakamaAccountDevice UnlinkedDevice; + UnlinkedDevice.Id = DeviceId; + + NakamaApi::AuthenticateDevice(Client, UnlinkedDevice, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + AddError(TEXT("Auth should have failed for unlinked device")); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + TestTrue("Unlinked device auth rejected", true); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("LinkDevice setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("LinkEmail", [this]() + { + LatentIt("should link an email", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("link_%s@example.com"), *GenerateShortId()); + FString Password = TEXT("password123!"); + + auto* Action = UNakamaClientLinkEmail::LinkEmail( + GetTransientPackage(), Client, Session, Email, Password, TMap()); + Action->Activate(); + + // Verify: can authenticate with the linked email + VerifyWhenComplete(Action, [this, Done, Email, Password]() + { + FNakamaAccountEmail LinkedEmail; + LinkedEmail.Email = Email; + LinkedEmail.Password = Password; + + NakamaApi::AuthenticateEmail(Client, LinkedEmail, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + TestTrue("Linked email auth succeeded", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Linked email auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UnlinkEmail", [this]() + { + LatentIt("should unlink an email", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("unlink_%s@example.com"), *GenerateShortId()); + FString Password = TEXT("password123!"); + + // Setup: link email via C++ + NakamaApi::LinkEmail(Client, Session, Email, Password, TMap(), + [this, Done, Email, Password]() + { + // Fire BP UnlinkEmail + auto* Action = UNakamaClientUnlinkEmail::UnlinkEmail( + GetTransientPackage(), Client, Session, Email, Password, TMap()); + Action->Activate(); + + // Verify: can no longer auth with the unlinked email + VerifyWhenComplete(Action, [this, Done, Email, Password]() + { + FNakamaAccountEmail UnlinkedEmail; + UnlinkedEmail.Email = Email; + UnlinkedEmail.Password = Password; + + NakamaApi::AuthenticateEmail(Client, UnlinkedEmail, false, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + AddError(TEXT("Auth should have failed for unlinked email")); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + TestTrue("Unlinked email auth rejected", true); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("LinkEmail setup failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); +} + +// ============================================================================ +// NOTIFICATIONS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPNotificationsSpec, "IntegrationTests.NakamaBlueprint.Notifications", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPNotificationsSpec) + +const FString FNakamaBPNotificationsSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPNotificationsSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPNotificationsSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("ListNotifications", [this]() + { + LatentIt("should list notifications", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientListNotifications::ListNotifications( + GetTransientPackage(), Client, Session, 100, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListNotifications(Client, Session, 100, TEXT(""), + [this, Done](const FNakamaNotificationList& Result) + { + // Fresh account has no notifications; just verify no error + TestTrue("Notification list call succeeded", true); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListNotifications failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// MATCHES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPMatchesSpec, "IntegrationTests.NakamaBlueprint.Matches", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPMatchesSpec) + +const FString FNakamaBPMatchesSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPMatchesSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPMatchesSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("ListMatches", [this]() + { + LatentIt("should list matches", [this](const FDoneDelegate& Done) + { + auto* Action = UNakamaClientListMatches::ListMatches( + GetTransientPackage(), Client, Session, 10, false, TEXT(""), 0, 100, TEXT("")); + Action->Activate(); + + // Verify via C++ + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::ListMatches(Client, Session, 10, false, TEXT(""), 0, 100, TEXT(""), + [this, Done](const FNakamaMatchList& Result) + { + TestTrue("ListMatches call succeeded", true); + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("ListMatches failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// EVENTS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaBPEventsSpec, "IntegrationTests.NakamaBlueprint.Events", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + + FNakamaClientConfig Client; + FNakamaSession Session; + + static const FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7350; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaBPEventsSpec) + +const FString FNakamaBPEventsSpec::ServerKey = TEXT("defaultkey"); +const FString FNakamaBPEventsSpec::Host = TEXT("127.0.0.1"); + +void FNakamaBPEventsSpec::Define() +{ + BeforeEach([this]() + { + Client = FNakamaClientConfig{ServerKey, Host, Port, false}; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + NakamaApi::AuthenticateCustom(Client, Account, true, TEXT(""), + [this, Done](const FNakamaSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + NakamaApi::DeleteAccount(Client, Session, + [Done]() { Done.Execute(); }, + [Done](const FNakamaError&) { Done.Execute(); } + ); + }); + + Describe("Event", [this]() + { + LatentIt("should send an event", [this](const FDoneDelegate& Done) + { + TMap Properties; + Properties.Add(TEXT("key1"), TEXT("value1")); + + auto* Action = UNakamaClientEvent::Event( + GetTransientPackage(), Client, Session, TEXT("bp_test_event"), TEXT(""), false, Properties); + Action->Activate(); + + // Verify: server is still healthy after event + VerifyWhenComplete(Action, [this, Done]() + { + NakamaApi::Healthcheck(Client, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FNakamaError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/NakamaRtTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/NakamaRtTests.cpp new file mode 100644 index 000000000..0a03abe26 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/NakamaRtTests.cpp @@ -0,0 +1,1160 @@ +/** + * NakamaRealtimeClient Integration Test Suite + * + * Tests the typed NakamaRealtimeClient API layer: ChannelJoin, MatchCreate, + * MatchmakerAdd, StatusUpdate, PartyCreate, and Ping. + * + * Each spec creates an isolated UGameInstance (via Init()) so the + * UNakamaWebSocketSubsystem is fresh per test, matching the isolation strategy + * used in NakamaWebSocketSubsystemTests.cpp. + * + * Requires a running Nakama server (IntegrationTests/server/docker-compose.yml). + * + * Run with: + * -ExecCmds="Automation RunTests IntegrationTests.NakamaRt" + */ + +#include "Misc/AutomationTest.h" +#include "Misc/Guid.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Nakama.h" +#include "NakamaRt.h" +#include "NakamaWebSocketSubsystem.h" +#include "WebSocketsModule.h" +#include "Engine/Engine.h" +#include "Engine/GameInstance.h" + +// --------------------------------------------------------------------------- +// Shared constants and helpers (file-local) +// --------------------------------------------------------------------------- + +namespace +{ + static const FString RtServerKey = TEXT("defaultkey"); + static const FString RtHost = TEXT("127.0.0.1"); + static constexpr int32 RtPort = 7350; + + FNakamaWebSocketConnectionParams MakeConnParams(const FNakamaSession& Session) + { + FNakamaWebSocketConnectionParams P; + P.Host = RtHost; + P.Port = RtPort; + P.Token = Session.Token; + P.PingIntervalSeconds = 60.0f; + P.bUseSSL = false; + return P; + } + + /** Create a UGameInstance with a fully-initialised subsystem collection. + * + * Calling Init() triggers SubsystemCollection.Initialize(), which + * auto-discovers and registers UNakamaWebSocketSubsystem (and any other + * UGameInstanceSubsystem subclasses). The caller must AddToRoot() the + * returned instance and call Shutdown() + RemoveFromRoot() after use. */ + UGameInstance* CreateTestGameInstance() + { + if (!FModuleManager::Get().IsModuleLoaded(TEXT("WebSockets"))) + { + FModuleManager::Get().LoadModule(TEXT("WebSockets")); + } + UGameInstance* GI = NewObject(GetTransientPackage()); + GI->Init(); + return GI; + } +} + +/** + * Fails the current test and calls Done when a FNakamaWebSocketResponse carries + * an error. Use only in terminal .Next() callbacks (it contains a bare return). + */ +#define RT_FAIL_ON_ERROR(Resp, Done) \ + if ((Resp).ErrorCode != ENakamaWebSocketError::None) { \ + AddError(FString::Printf(TEXT("Unexpected realtime error in response: %d"), Resp.ErrorCode)); \ + (Done).Execute(); \ + return; \ + } + +// ============================================================================ +// PING TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtPingSpec, "IntegrationTests.NakamaRt.Ping", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaRtPingSpec) + +void FNakamaRtPingSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Single", [this]() + { + LatentIt("should receive a pong from Ping()", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->Ping(); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// CHANNEL TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtChannelSpec, "IntegrationTests.NakamaRt.Channel", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateRoomName() { return TEXT("rt-test-") + FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + + FString ExtractChannelId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("channel"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("id"), Id); + } + return Id; + } + + FString ExtractMessageId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("channel_message_ack"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("message_id"), Id); + } + return Id; + } + +END_DEFINE_SPEC(FNakamaRtChannelSpec) + +void FNakamaRtChannelSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + + Describe("Join", [this]() + { + LatentIt("should join a room channel and receive a channel ID", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->ChannelJoin(GenerateRoomName(), 1, false, false); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + TestFalse("Joined channel should have a non-empty ID", ExtractChannelId(Resp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("SendMessage", [this]() + { + LatentIt("should send a message and receive an ack with a message_id", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->ChannelJoin(GenerateRoomName(), 1, true, false); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelJoin failed")); + return MakeCompletedFuture(JoinResp); + } + FString ChannelId = ExtractChannelId(JoinResp); + TestFalse("Channel ID from join", ChannelId.IsEmpty()); + return RtClient->ChannelMessageSend(ChannelId, TEXT("{\"msg\":\"hello\"}")); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + TestFalse("Ack should contain a message_id", ExtractMessageId(Resp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("UpdateMessage", [this]() + { + LatentIt("should update a sent message without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + FString ChannelId; + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->ChannelJoin(GenerateRoomName(), 1, true, false); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelJoin failed")); + return MakeCompletedFuture(JoinResp); + } + return RtClient->ChannelMessageSend(ExtractChannelId(JoinResp), TEXT("{\"msg\":\"original\"}")); + }) + .Next([this](FNakamaWebSocketResponse SendResp) -> TNakamaFuture + { + if (SendResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelMessageSend failed")); + return MakeCompletedFuture(SendResp); + } + // Extract both channel_id and message_id from the ack + FString MsgId = ExtractMessageId(SendResp); + FString ChId; + const TSharedPtr* Ack; + if (SendResp.Data.IsValid() && SendResp.Data->TryGetObjectField(TEXT("channel_message_ack"), Ack)) + { + (*Ack)->TryGetStringField(TEXT("channel_id"), ChId); + } + TestFalse("Message ID before update", MsgId.IsEmpty()); + return RtClient->ChannelMessageUpdate(ChId, MsgId, TEXT("{\"msg\":\"updated\"}")); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); + + Describe("RemoveMessage", [this]() + { + LatentIt("should remove a sent message without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->ChannelJoin(GenerateRoomName(), 1, true, false); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelJoin failed")); + return MakeCompletedFuture(JoinResp); + } + return RtClient->ChannelMessageSend(ExtractChannelId(JoinResp), TEXT("{\"msg\":\"to delete\"}")); + }) + .Next([this](FNakamaWebSocketResponse SendResp) -> TNakamaFuture + { + if (SendResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelMessageSend failed")); + return MakeCompletedFuture(SendResp); + } + FString MsgId = ExtractMessageId(SendResp); + FString ChId; + const TSharedPtr* Ack; + if (SendResp.Data.IsValid() && SendResp.Data->TryGetObjectField(TEXT("channel_message_ack"), Ack)) + { + (*Ack)->TryGetStringField(TEXT("channel_id"), ChId); + } + return RtClient->ChannelMessageRemove(ChId, MsgId); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); + + Describe("Leave", [this]() + { + LatentIt("should leave a channel without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->ChannelJoin(GenerateRoomName(), 1, false, false); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("ChannelJoin failed")); + return MakeCompletedFuture(JoinResp); + } + return RtClient->ChannelLeave(ExtractChannelId(JoinResp)); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCH TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtMatchSpec, "IntegrationTests.NakamaRt.Match", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + FString ExtractMatchId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("match"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("match_id"), Id); + } + return Id; + } + +END_DEFINE_SPEC(FNakamaRtMatchSpec) + +void FNakamaRtMatchSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + + Describe("Create", [this]() + { + LatentIt("should create a relayed match and receive a match_id", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchCreate(TEXT("")); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + TestFalse("match_id should not be empty", ExtractMatchId(Resp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Leave", [this]() + { + LatentIt("should leave a match without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchCreate(TEXT("")); + }) + .Next([this](FNakamaWebSocketResponse CreateResp) -> TNakamaFuture + { + if (CreateResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("MatchCreate failed")); + return MakeCompletedFuture(CreateResp); + } + FString MatchId = ExtractMatchId(CreateResp); + TestFalse("Match ID before leave", MatchId.IsEmpty()); + return RtClient->MatchLeave(MatchId); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); + + Describe("SendData", [this]() + { + LatentIt("should send match data without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchCreate(TEXT("")); + }) + .Next([this, Done](FNakamaWebSocketResponse CreateResp) + { + RT_FAIL_ON_ERROR(CreateResp, Done); + FString MatchId = ExtractMatchId(CreateResp); + TArray Payload = { 1, 2, 3 }; + // MatchDataSend is fire-and-forget — the server sends no response. + RtClient->MatchDataSend(MatchId, 1, Payload, {}, true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCHMAKER TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtMatchmakerSpec, "IntegrationTests.NakamaRt.Matchmaker", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + FString ExtractTicket(const FNakamaWebSocketResponse& Resp) const + { + FString Ticket; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("matchmaker_ticket"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("ticket"), Ticket); + } + return Ticket; + } + +END_DEFINE_SPEC(FNakamaRtMatchmakerSpec) + +void FNakamaRtMatchmakerSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + + Describe("Add", [this]() + { + LatentIt("should add to matchmaker and receive a ticket", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchmakerAdd(2, 4, TEXT("*"), 2, {}, {}); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + TestFalse("Matchmaker ticket should not be empty", ExtractTicket(Resp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Remove", [this]() + { + LatentIt("should cancel matchmaking with the returned ticket", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchmakerAdd(2, 4, TEXT("*"), 2, {}, {}); + }) + .Next([this](FNakamaWebSocketResponse AddResp) -> TNakamaFuture + { + if (AddResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("MatchmakerAdd failed")); + return MakeCompletedFuture(AddResp); + } + FString Ticket = ExtractTicket(AddResp); + TestFalse("Ticket before remove", Ticket.IsEmpty()); + return RtClient->MatchmakerRemove(Ticket); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCH JOIN TESTS (two-client: match_id path and matchmaker-token path) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtMatchJoinSpec, "IntegrationTests.NakamaRt.MatchJoin", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + + // Client A — set up by BeforeEach + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + // Client B — set up inline per test + FNakamaSession Session2; + UGameInstance* GI2 = nullptr; + TUniquePtr RtClient2; + + // Shared state threaded through the JoinByMatchId chain via a member + FString PendingMatchId; + + // Counter used by the JoinByToken test — Done fires when both clients joined + int32 MatchJoinedCount = 0; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + FString ExtractMatchId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("match"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("match_id"), Id); + } + return Id; + } + + /** Extract the matchmaker token from a raw server-push JSON string. */ + FString ExtractMatchmakerToken(const FString& RawJson) const + { + TSharedPtr Root; + TSharedRef> Reader = TJsonReaderFactory<>::Create(RawJson); + if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid()) + { + return TEXT(""); + } + const TSharedPtr* MatchedObj; + if (!Root->TryGetObjectField(TEXT("matchmaker_matched"), MatchedObj)) + { + return TEXT(""); + } + FString Token; + (*MatchedObj)->TryGetStringField(TEXT("token"), Token); + return Token; + } + +END_DEFINE_SPEC(FNakamaRtMatchJoinSpec) + +void FNakamaRtMatchJoinSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + PendingMatchId = TEXT(""); + MatchJoinedCount = 0; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth A: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + RtClient2.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + if (GI2) { GI2->Shutdown(); GI2->RemoveFromRoot(); GI2 = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session) + .Next([this, Done](FNakamaVoidResult) + { + Session = {}; + if (Session2.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session2) + .Next([this, Done](FNakamaVoidResult) { Session2 = {}; Done.Execute(); }); + }); + }); + + // ----------------------------------------------------------------------- + + Describe("JoinByMatchId", [this]() + { + LatentIt("should join a relayed match created by another client using match_id", [this](const FDoneDelegate& Done) + { + // Chain: Connect A → Create Match → Auth B → Connect B → MatchJoin(match_id) + // PendingMatchId threads the match_id through the chain via spec member state. + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect A", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->MatchCreate(TEXT("")); + }) + .Next([this, Done](FNakamaWebSocketResponse CreateResp) -> TNakamaFuture + { + if (CreateResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("MatchCreate failed")); + return MakeCompletedFuture(FNakamaSessionResult{}); + } + PendingMatchId = ExtractMatchId(CreateResp); + TestFalse("match_id from MatchCreate", PendingMatchId.IsEmpty()); + + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")); + }) + .Next([this, Done](FNakamaSessionResult R2) -> TNakamaFuture + { + if (R2.bIsError) + { + AddError(FString::Printf(TEXT("Auth B: %s"), *R2.Error.Message)); + Done.Execute(); + return MakeCompletedFuture(FNakamaWebSocketConnectionResult{}); + } + Session2 = R2.Value; + GI2 = CreateTestGameInstance(); + GI2->AddToRoot(); + RtClient2 = MakeUnique(GI2); + return RtClient2->Connect(MakeConnParams(Session2)); + }) + .Next([this](FNakamaWebSocketConnectionResult CR2) -> TNakamaFuture + { + TestFalse("Connect B", CR2.ErrorCode != ENakamaWebSocketError::None); + return RtClient2->MatchJoin(PendingMatchId, TEXT(""), {}); + }) + .Next([this, Done](FNakamaWebSocketResponse JoinResp) + { + RT_FAIL_ON_ERROR(JoinResp, Done); + TestFalse("Client B joined with non-empty match_id", ExtractMatchId(JoinResp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("JoinByToken", [this]() + { + LatentIt("should join a match via matchmaker token when two clients are matched", [this](const FDoneDelegate& Done) + { + // Chain: Auth B → Connect B → Connect A → subscribe push events → MatchmakerAdd A → MatchmakerAdd B + // The server fires "matchmaker_matched" (no CID, push) to both clients. + // ServerEventReceived handlers extract the token and call MatchJoin; Done fires when both joined. + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R2) -> TNakamaFuture + { + if (R2.bIsError) { AddError(FString::Printf(TEXT("Auth B: %s"), *R2.Error.Message)); Done.Execute(); return MakeCompletedFuture(FNakamaWebSocketConnectionResult{}); } + Session2 = R2.Value; + GI2 = CreateTestGameInstance(); + GI2->AddToRoot(); + RtClient2 = MakeUnique(GI2); + return RtClient2->Connect(MakeConnParams(Session2)); + }) + .Next([this, Done](FNakamaWebSocketConnectionResult CR2) -> TNakamaFuture + { + TestFalse("Connect B", CR2.ErrorCode != ENakamaWebSocketError::None); + RtClient = MakeUnique(GI); + return RtClient->Connect(MakeConnParams(Session)); + }) + .Next([this, Done](FNakamaWebSocketConnectionResult CR1) -> TNakamaFuture + { + TestFalse("Connect A", CR1.ErrorCode != ENakamaWebSocketError::None); + + // Helper: subscribe a subsystem to matchmaker_matched push events and call MatchJoin. + // Both clients subscribe before either adds to the matchmaker queue. + auto SubscribeAndJoin = [this, Done]( + UNakamaWebSocketSubsystem* Sub, + Nakama::NakamaRealtimeClient* Client, + const FString& ClientLabel) + { + Sub->ServerEventReceived.AddLambda( + [this, Done, Client, ClientLabel](const FString& Msg) + { + FString Token = ExtractMatchmakerToken(Msg); + if (Token.IsEmpty()) { return; } // not a matchmaker_matched event + Client->MatchJoin(TEXT(""), Token, {}) + .Next([this, Done, ClientLabel](FNakamaWebSocketResponse JoinResp) + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(FString::Printf(TEXT("%s MatchJoin failed: %d"), *ClientLabel, JoinResp.ErrorCode)); + Done.Execute(); + return; + } + if (++MatchJoinedCount == 2) + { + Done.Execute(); + } + }); + }); + }; + + SubscribeAndJoin(GI->GetSubsystem(), RtClient.Get(), TEXT("Client A")); + SubscribeAndJoin(GI2->GetSubsystem(), RtClient2.Get(), TEXT("Client B")); + + return RtClient->MatchmakerAdd(2, 2, TEXT("*"), {}, {}, {}); + }) + .Next([this, Done](FNakamaWebSocketResponse AddAResp) -> TNakamaFuture + { + if (AddAResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(FString::Printf(TEXT("MatchmakerAdd A failed: %d"), AddAResp.ErrorCode)); + Done.Execute(); + return MakeCompletedFuture(AddAResp); + } + return RtClient2->MatchmakerAdd(2, 2, TEXT("*"), {}, {}, {}); + }) + .Next([this, Done](FNakamaWebSocketResponse AddBResp) + { + if (AddBResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(FString::Printf(TEXT("MatchmakerAdd B failed: %d"), AddBResp.ErrorCode)); + Done.Execute(); + } + // Both clients are now queued. The server will push matchmaker_matched to each. + // The ServerEventReceived handlers extract the token, call MatchJoin, and + // call Done once MatchJoinedCount reaches 2. + }); + }); + }); +} + +// ============================================================================ +// STATUS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtStatusSpec, "IntegrationTests.NakamaRt.Status", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaRtStatusSpec) + +void FNakamaRtStatusSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + + Describe("Update", [this]() + { + LatentIt("should set a status string without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->StatusUpdate(TEXT("Playing a game")); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + + LatentIt("should clear status by passing an empty string", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->StatusUpdate(TEXT("")); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); + + Describe("Follow", [this]() + { + LatentIt("should follow a user ID without error", [this](const FDoneDelegate& Done) + { + // Nakama accepts follow requests for non-existent users and returns + // an empty presence list, so a random UUID is fine here. + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->StatusFollow({ GenerateId() }, {}); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + + LatentIt("should follow and then unfollow a user ID", [this](const FDoneDelegate& Done) + { + FString FakeUserId = GenerateId(); + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this, FakeUserId](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->StatusFollow({ FakeUserId }, {}); + }) + .Next([this, FakeUserId](FNakamaWebSocketResponse FollowResp) -> TNakamaFuture + { + if (FollowResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("StatusFollow failed")); + return MakeCompletedFuture(FollowResp); + } + return RtClient->StatusUnfollow({ FakeUserId }); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// PARTY TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtPartySpec, "IntegrationTests.NakamaRt.Party", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UGameInstance* GI = nullptr; + TUniquePtr RtClient; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + FString ExtractPartyId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* Obj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("party"), Obj)) + { + (*Obj)->TryGetStringField(TEXT("party_id"), Id); + } + return Id; + } + +END_DEFINE_SPEC(FNakamaRtPartySpec) + +void FNakamaRtPartySpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + GI = CreateTestGameInstance(); + GI->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + RtClient.Reset(); + if (GI) { GI->Shutdown(); GI->RemoveFromRoot(); GI = nullptr; } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + + Describe("Create", [this]() + { + LatentIt("should create a party and receive a party_id", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->PartyCreate(true, 4, TEXT(""), false); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + TestFalse("party_id should not be empty", ExtractPartyId(Resp).IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Close", [this]() + { + LatentIt("should close a party without error", [this](const FDoneDelegate& Done) + { + RtClient = MakeUnique(GI); + RtClient->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return RtClient->PartyCreate(true, 4, TEXT(""), false); + }) + .Next([this](FNakamaWebSocketResponse CreateResp) -> TNakamaFuture + { + if (CreateResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("PartyCreate failed")); + return MakeCompletedFuture(CreateResp); + } + FString PartyId = ExtractPartyId(CreateResp); + TestFalse("Party ID before close", PartyId.IsEmpty()); + return RtClient->PartyClose(PartyId); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + RT_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// SUBSYSTEM LIFETIME TESTS (no server required) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaRtSubsystemLifetimeSpec, "IntegrationTests.NakamaRt.SubsystemLifetime", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + TUniquePtr RtClient; + +END_DEFINE_SPEC(FNakamaRtSubsystemLifetimeSpec) + +void FNakamaRtSubsystemLifetimeSpec::Define() +{ + AfterEach([this]() + { + RtClient.Reset(); + }); + + Describe("StaledSubsystem", [this]() + { + LatentIt("should return an error future instead of crashing when subsystem is destroyed", [this](const FDoneDelegate& Done) + { + // 1. Stand up a fresh GameInstance — this creates UNakamaWebSocketSubsystem. + UGameInstance* GI = CreateTestGameInstance(); + GI->AddToRoot(); + RtClient = MakeUnique(GI); + + // 2. Tear down the GameInstance, simulating a level transition or PIE stop. + // UNakamaWebSocketSubsystem is marked for GC; the TWeakObjectPtr + // inside RtClient goes stale on the next collection pass. + GI->Shutdown(); + GI->RemoveFromRoot(); + GI = nullptr; + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // 3. Call a method while the subsystem is stale. + // Without the validity guard the generated template emits + // WebSocketSubsystem->Send(...) + // on a null/stale pointer — guaranteed crash. + // After the fix it must immediately resolve to an error. + RtClient->Ping() + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + TestNotEqual( + TEXT("Stale-subsystem call must return an error, not None"), + Resp.ErrorCode, + ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/NakamaTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/NakamaTests.cpp new file mode 100644 index 000000000..3d7184f96 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/NakamaTests.cpp @@ -0,0 +1,5361 @@ +/** + * Nakama Async API Integration Test Suite + * + * Mirror of NakamaApiTests.cpp using the TFuture-based Nakama:: free-function API. + * Each test uses F*Result types instead of separate success/error callback pairs. + */ + +#include +#include "Nakama.h" +#include "Misc/Guid.h" +#include "Serialization/JsonSerializer.h" + +const FNakamaClientConfig ClientConfig = FNakamaClientConfig{TEXT("defaultkey"), TEXT("127.0.0.1"), 7350, false}; + +/** + * Helper macro: early-return on unexpected error inside a callback. + * Use in tests that expect success. + */ +#define ASYNC_FAIL_ON_ERROR(Result, Done) \ + if (Result.bIsError) { \ + AddError(FString::Printf(TEXT("Unexpected error %d: %s"), Result.Error.Code, *Result.Error.Message)); \ + Done.Execute(); \ + return; \ + } + +// ============================================================================ +// AUTHENTICATION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAuthSpec, "IntegrationTests.Nakama.Auth", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + + FString TestCustomId; + FString TestDeviceId; + FString TestEmail; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncAuthSpec) + +void FNakamaAsyncAuthSpec::Define() +{ + BeforeEach([this]() + { + TestCustomId = GenerateId(); + TestDeviceId = GenerateId(); + TestEmail = FString::Printf(TEXT("test_%s@example.com"), *GenerateShortId()); + }); + + Describe("CustomAuth", [this]() + { + LatentIt("should authenticate with valid custom ID", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, TestCustomId, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + TestTrue("Session has refresh token", !Result.Value.RefreshToken.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + }); + + LatentIt("should fail with empty custom ID", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, TEXT(""), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with custom ID too short < 6 chars", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, TEXT("abc"), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with custom ID too long > 128 chars", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, FString::ChrN(150, 'a'), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should authenticate with username", [this](const FDoneDelegate& Done) + { + FString Username = FString::Printf(TEXT("user_%s"), *GenerateShortId()); + Nakama::AuthenticateCustom(ClientConfig, TestCustomId, true, Username).Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + }); + }); + + Describe("DeviceAuth", [this]() + { + LatentIt("should authenticate with valid device ID", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateDevice(ClientConfig, TestDeviceId, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + TestTrue("Session has refresh token", !Result.Value.RefreshToken.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + }); + + LatentIt("should fail with empty device ID", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateDevice(ClientConfig, TEXT(""), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with device ID too short < 10 chars", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateDevice(ClientConfig, TEXT("short"), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("EmailAuth", [this]() + { + LatentIt("should authenticate with valid email and password", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateEmail(ClientConfig, TestEmail, TEXT("password123"), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + }); + + LatentIt("should fail with invalid email format", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateEmail(ClientConfig, TEXT("notanemail"), TEXT("password123"), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with password too short < 8 chars", [this](const FDoneDelegate& Done) + { + Nakama::AuthenticateEmail(ClientConfig, TestEmail, TEXT("short"), true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ACCOUNT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAccountSpec, "IntegrationTests.Nakama.Account", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncAccountSpec) + + +void FNakamaAsyncAccountSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("GetAccount", [this]() + { + LatentIt("should get account for authenticated user", [this](const FDoneDelegate& Done) + { + Nakama::GetAccount(ClientConfig, Session).Next([this, Done](FNakamaAccountResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Account has user ID", !Result.Value.User.Id.IsEmpty()); + UserId = Result.Value.User.Id; + Done.Execute(); + }); + }); + }); + + Describe("UpdateAccount", [this]() + { + LatentIt("should update account display name", [this](const FDoneDelegate& Done) + { + FString NewDisplayName = FString::Printf(TEXT("TestUser_%s"), *GenerateShortId()); + + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), NewDisplayName, TEXT(""), TEXT("en"), TEXT(""), TEXT("")) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done, NewDisplayName](FNakamaAccountResult GetResult) + { + ASYNC_FAIL_ON_ERROR(GetResult, Done); + TestEqual("Display name updated", GetResult.Value.User.DisplayName, NewDisplayName); + Done.Execute(); + }); + }); + }); + + Describe("SessionRefresh", [this]() + { + LatentIt("should refresh session with valid token", [this](const FDoneDelegate& Done) + { + Nakama::SessionRefresh(ClientConfig, Session.RefreshToken, TMap()) + .Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("New session has token", !Result.Value.Token.IsEmpty()); + TestTrue("New session has refresh token", !Result.Value.RefreshToken.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid refresh token", [this](const FDoneDelegate& Done) + { + Nakama::SessionRefresh(ClientConfig, TEXT("invalid.token.here"), TMap()) + .Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) + { + AddError(TEXT("Expected error but got success")); + Done.Execute(); + return; + } + TestTrue("Got error response", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// FRIENDS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncFriendsSpec, "IntegrationTests.Nakama.Friends", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession FriendSession; + FString UserId; + FString FriendUserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncFriendsSpec) + + +void FNakamaAsyncFriendsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom FriendAccount; + FriendAccount.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, FriendAccount.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& FriendAuthResult) + { + FriendSession = FriendAuthResult; + return Nakama::GetAccount(ClientConfig, FriendSession); + }) + .Next([this, Done](FNakamaAccountResult FriendAccResult) + { + ASYNC_FAIL_ON_ERROR(FriendAccResult, Done); + FriendUserId = FriendAccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + auto DeleteMain = [this, Done]() + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }; + + if (FriendSession.Token.IsEmpty()) { DeleteMain(); return; } + Nakama::DeleteAccount(ClientConfig, FriendSession).Next([DeleteMain](FNakamaVoidResult) { DeleteMain(); }); + }); + + + + Describe("ListFriends", [this]() + { + LatentIt("should list friends for authenticated user", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 100, 0, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Friends list is valid", true); + Done.Execute(); + }); + }); + }); + + Describe("AddFriends", [this]() + { + LatentIt("should add friend by user ID", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(FriendUserId); + + Nakama::AddFriends(ClientConfig, Session, Ids, TArray(), TEXT("")).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("AddFriends completed successfully", true); + Done.Execute(); + }); + }); + + LatentIt("should not add self as friend", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(UserId); + + Nakama::AddFriends(ClientConfig, Session, Ids, TArray(), TEXT("")).Next([this, Done](FNakamaVoidResult AddResult) + { + // Server accepts but should not add self to friend list + if (AddResult.bIsError) + { + // Some servers may return an error, which is also acceptable + Done.Execute(); + return; + } + + // Verify self is not in friend list + Nakama::ListFriends(ClientConfig, Session, 100, 2, TEXT("")).Next([this, Done](FNakamaFriendListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + bool bFoundSelf = false; + for (const auto& Friend : ListResult.Value.Friends) + { + if (Friend.User.Id == UserId) + { + bFoundSelf = true; + break; + } + } + TestTrue("Self not in friend list", !bFoundSelf); + Done.Execute(); + }); + }); + }); + }); + + Describe("BlockFriends", [this]() + { + LatentIt("should block user by ID", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(FriendUserId); + + Nakama::BlockFriends(ClientConfig, Session, Ids, TArray()).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Block succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// GROUPS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncGroupsSpec, "IntegrationTests.Nakama.Groups", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + FString CreatedGroupId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncGroupsSpec) + + +void FNakamaAsyncGroupsSpec::Define() +{ + BeforeEach([this]() + { + CreatedGroupId.Empty(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("CreateGroup", [this]() + { + LatentIt("should create group with valid name", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("TestGroup_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test description"), TEXT(""), TEXT("en"), true, 100) + .Next([this, Done](FNakamaGroupResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Group has ID", !Result.Value.Id.IsEmpty()); + TestTrue("Group has name", !Result.Value.Name.IsEmpty()); + CreatedGroupId = Result.Value.Id; + Done.Execute(); + }); + }); + + LatentIt("should fail with empty group name", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, TEXT(""), TEXT("Description"), TEXT(""), TEXT("en"), true, 100) + .Next([this, Done](FNakamaGroupResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty name", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ListGroups", [this]() + { + LatentIt("should list groups", [this](const FDoneDelegate& Done) + { + Nakama::ListGroups(ClientConfig, Session, TEXT(""), TEXT(""), 100, TEXT(""), 0, true) + .Next([this, Done](FNakamaGroupListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Groups list is valid", true); + Done.Execute(); + }); + }); + }); + + Describe("ListUserGroups", [this]() + { + LatentIt("should list groups for user", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("UserGroup_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup&) + { + return Nakama::ListUserGroups(ClientConfig, Session, UserId, 100, 0, TEXT("")); + }) + .Next([this, Done](FNakamaUserGroupListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + TestTrue("User has at least one group", ListResult.Value.UserGroups.Num() >= 1); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// STORAGE TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncStorageSpec, "IntegrationTests.Nakama.Storage", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncStorageSpec) + + +void FNakamaAsyncStorageSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("WriteStorageObjects", [this]() + { + LatentIt("should write storage object", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("test_collection"); + Obj.Key = FString::Printf(TEXT("test_key_%s"), *GenerateShortId()); + Obj.Value = TEXT("{\"test\": \"value\"}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Got acks", Result.Value.Acks.Num() > 0); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid JSON value", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("test_collection"); + Obj.Key = TEXT("test_key"); + Obj.Value = TEXT("not valid json {{{"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid JSON", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ReadStorageObjects", [this]() + { + LatentIt("should read written storage object", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("read_key_%s"), *GenerateShortId()); + + TArray WriteObjects; + FNakamaWriteStorageObject WriteObj; + WriteObj.Collection = TEXT("test_collection"); + WriteObj.Key = Key; + WriteObj.Value = TEXT("{\"data\": \"test\"}"); + WriteObj.PermissionRead = 2; + WriteObj.PermissionWrite = 1; + WriteObjects.Add(WriteObj); + + Nakama::WriteStorageObjects(ClientConfig, Session, WriteObjects) + .Next([this, Key](const FNakamaStorageObjectAcks&) + { + TArray ReadIds; + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("test_collection"); + ReadId.Key = Key; + ReadId.UserId = UserId; + ReadIds.Add(ReadId); + + return Nakama::ReadStorageObjects(ClientConfig, Session, ReadIds); + }) + .Next([this, Done](FNakamaStorageObjectsResult ReadResult) + { + ASYNC_FAIL_ON_ERROR(ReadResult, Done); + TestTrue("Got objects", ReadResult.Value.Objects.Num() > 0); + Done.Execute(); + }); + }); + }); + + Describe("ListStorageObjects", [this]() + { + LatentIt("should list storage objects", [this](const FDoneDelegate& Done) + { + FString ShortId = GenerateShortId(); + TArray Objects; + for (int32 i = 0; i < 3; i++) + { + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("list_collection"); + Obj.Key = FString::Printf(TEXT("list_key_%d_%s"), i, *ShortId); + Obj.Value = FString::Printf(TEXT("{\"index\": %d}"), i); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + } + Nakama::WriteStorageObjects(ClientConfig, Session, Objects) + .Next([this](const FNakamaStorageObjectAcks&) + { + return Nakama::ListStorageObjects(ClientConfig, Session, UserId, TEXT("list_collection"), 100, TEXT("")); + }) + .Next([this, Done](FNakamaStorageObjectListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + TestTrue("Has objects", ListResult.Value.Objects.Num() >= 3); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// LEADERBOARD TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncLeaderboardSpec, "IntegrationTests.Nakama.Leaderboard", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncLeaderboardSpec) + + +void FNakamaAsyncLeaderboardSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("WriteLeaderboardRecord", [this]() + { + LatentIt("should fail with empty leaderboard ID", [this](const FDoneDelegate& Done) + { + // FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record; + // Record.Score = 100; + // Record.Subscore = 0; + // Record.Metadata = TEXT("{}"); + // Record.Operator = ENakamaOperator::NO_OVERRIDE; + + Nakama::WriteLeaderboardRecord(ClientConfig, Session, TEXT(""), 100, 0, TEXT("{}"), ENakamaOperator::NO_OVERRIDE) + .Next([this, Done](FNakamaLeaderboardRecordResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty leaderboard ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with non-existent leaderboard", [this](const FDoneDelegate& Done) + { + //FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record; + //Record.Score = 100; + //Record.Subscore = 0; + //Record.Metadata = TEXT("{}"); + //Record.Operator = ENakamaOperator::NO_OVERRIDE; + + Nakama::WriteLeaderboardRecord(ClientConfig, Session, TEXT("nonexistent_leaderboard_12345"), 100) + .Next([this, Done](FNakamaLeaderboardRecordResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got NOT_FOUND error", Result.Error.Code == 5 || Result.Error.Message.Contains(TEXT("not found"))); + Done.Execute(); + }); + }); + }); + + Describe("ListLeaderboardRecords", [this]() + { + LatentIt("should fail with empty leaderboard ID", [this](const FDoneDelegate& Done) + { + Nakama::ListLeaderboardRecords(ClientConfig, Session, TEXT(""), TArray(), 100, TEXT(""), 0) + .Next([this, Done](FNakamaLeaderboardRecordListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with limit too high", [this](const FDoneDelegate& Done) + { + Nakama::ListLeaderboardRecords(ClientConfig, Session, TEXT("test_leaderboard"), TArray(), 2000, TEXT(""), 0) + .Next([this, Done](FNakamaLeaderboardRecordListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for limit too high", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("DeleteLeaderboardRecord", [this]() + { + LatentIt("should fail with empty leaderboard ID", [this](const FDoneDelegate& Done) + { + Nakama::DeleteLeaderboardRecord(ClientConfig, Session, TEXT("")) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty leaderboard ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCHES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncMatchesSpec, "IntegrationTests.Nakama.Matches", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncMatchesSpec) + + +void FNakamaAsyncMatchesSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListMatches", [this]() + { + LatentIt("should list matches", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 10, false, TEXT(""), 0, 100, TEXT("")) + .Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Matches list is valid", true); + Done.Execute(); + }); + }); + + LatentIt("should list authoritative matches only", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 10, true, TEXT(""), 0, 100, TEXT("")) + .Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Authoritative matches list is valid", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// NOTIFICATIONS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncNotificationsSpec, "IntegrationTests.Nakama.Notifications", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncNotificationsSpec) + + +void FNakamaAsyncNotificationsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListNotifications", [this]() + { + LatentIt("should list notifications", [this](const FDoneDelegate& Done) + { + Nakama::ListNotifications(ClientConfig, Session, 100, TEXT("")) + .Next([this, Done](FNakamaNotificationListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Notifications list is valid", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// LINK TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncLinkSpec, "IntegrationTests.Nakama.Link", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString DeviceId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncLinkSpec) + + +void FNakamaAsyncLinkSpec::Define() +{ + BeforeEach([this]() + { + DeviceId = GenerateId(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountDevice Account; + Account.Id = DeviceId; + + Nakama::AuthenticateDevice(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("LinkCustom", [this]() + { + LatentIt("should link custom ID", [this](const FDoneDelegate& Done) + { + Nakama::LinkCustom(ClientConfig, Session, GenerateId(), TMap()) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Link succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with ID too short", [this](const FDoneDelegate& Done) + { + Nakama::LinkCustom(ClientConfig, Session, TEXT("abc"), TMap()) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for short ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("LinkEmail", [this]() + { + LatentIt("should link email", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("link_%s@example.com"), *GenerateShortId()); + + Nakama::LinkEmail(ClientConfig, Session, Email, TEXT("password123"), TMap()) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Link succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid email format", [this](const FDoneDelegate& Done) + { + Nakama::LinkEmail(ClientConfig, Session, TEXT("notanemail"), TEXT("password123"), TMap()) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid email", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with password too short", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("short_%s@example.com"), *GenerateShortId()); + + Nakama::LinkEmail(ClientConfig, Session, Email, TEXT("short"), TMap()) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for short password", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// TOURNAMENT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncTournamentSpec, "IntegrationTests.Nakama.Tournament", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncTournamentSpec) + + +void FNakamaAsyncTournamentSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListTournaments", [this]() + { + LatentIt("should list tournaments", [this](const FDoneDelegate& Done) + { + Nakama::ListTournaments(ClientConfig, Session, 0, 127, 0, 0, 100, TEXT("")) + .Next([this, Done](FNakamaTournamentListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Tournaments list is valid", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with category end too high", [this](const FDoneDelegate& Done) + { + Nakama::ListTournaments(ClientConfig, Session, 0, 200, 0, 0, 100, TEXT("")) + .Next([this, Done](FNakamaTournamentListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid category", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with limit too high", [this](const FDoneDelegate& Done) + { + Nakama::ListTournaments(ClientConfig, Session, 0, 127, 0, 0, 2000, TEXT("")) + .Next([this, Done](FNakamaTournamentListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for limit too high", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("JoinTournament", [this]() + { + LatentIt("should fail with empty tournament ID", [this](const FDoneDelegate& Done) + { + Nakama::JoinTournament(ClientConfig, Session, TEXT("")) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty tournament ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with non-existent tournament", [this](const FDoneDelegate& Done) + { + Nakama::JoinTournament(ClientConfig, Session, TEXT("nonexistent_tournament_12345")) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got NOT_FOUND error", Result.Error.Code == 5 || Result.Error.Message.Contains(TEXT("not found"))); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// USERS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncUsersSpec, "IntegrationTests.Nakama.Users", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + FString Username; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncUsersSpec) + + +void FNakamaAsyncUsersSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Username = FString::Printf(TEXT("user_%s"), *GenerateShortId()); + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, Username) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("GetUsers", [this]() + { + LatentIt("should get users by ID", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(UserId); + + Nakama::GetUsers(ClientConfig, Session, Ids, TArray(), TArray()) + .Next([this, Done](FNakamaUsersResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Got user", Result.Value.Users.Num() > 0); + if (Result.Value.Users.Num() > 0) + { + TestEqual("User ID matches", Result.Value.Users[0].Id, UserId); + } + Done.Execute(); + }); + }); + + LatentIt("should get users by username", [this](const FDoneDelegate& Done) + { + TArray Usernames; + Usernames.Add(Username); + + Nakama::GetUsers(ClientConfig, Session, TArray(), Usernames, TArray()) + .Next([this, Done](FNakamaUsersResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Got user by username", Result.Value.Users.Num() > 0); + Done.Execute(); + }); + }); + + LatentIt("should return empty for non-existent user", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(TEXT("ffffffff-ffff-ffff-ffff-ffffffffffff")); + + Nakama::GetUsers(ClientConfig, Session, Ids, TArray(), TArray()) + .Next([this, Done](FNakamaUsersResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Empty result for non-existent user", Result.Value.Users.Num() == 0); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// SESSION LOGOUT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncSessionSpec, "IntegrationTests.Nakama.Session", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncSessionSpec) + + +void FNakamaAsyncSessionSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("SessionLogout", [this]() + { + LatentIt("should logout successfully", [this](const FDoneDelegate& Done) + { + Nakama::SessionLogout(ClientConfig, Session, Session.Token, Session.RefreshToken) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Logout succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// DELETE STORAGE TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncDeleteStorageSpec, "IntegrationTests.Nakama.DeleteStorage", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncDeleteStorageSpec) + + +void FNakamaAsyncDeleteStorageSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("DeleteStorageObjects", [this]() + { + LatentIt("should delete storage object", [this](const FDoneDelegate& Done) + { + FString Key = FString::Printf(TEXT("delete_key_%s"), *GenerateShortId()); + + TArray WriteObjects; + FNakamaWriteStorageObject WriteObj; + WriteObj.Collection = TEXT("delete_collection"); + WriteObj.Key = Key; + WriteObj.Value = TEXT("{\"test\": \"delete\"}"); + WriteObj.PermissionRead = 1; + WriteObj.PermissionWrite = 1; + WriteObjects.Add(WriteObj); + + Nakama::WriteStorageObjects(ClientConfig, Session, WriteObjects) + .Next([this, Key](const FNakamaStorageObjectAcks&) + { + TArray DeleteIds; + FNakamaDeleteStorageObjectId DeleteId; + DeleteId.Collection = TEXT("delete_collection"); + DeleteId.Key = Key; + DeleteIds.Add(DeleteId); + + return Nakama::DeleteStorageObjects(ClientConfig, Session, DeleteIds); + }) + .Next([this, Done](FNakamaVoidResult DeleteResult) + { + ASYNC_FAIL_ON_ERROR(DeleteResult, Done); + TestTrue("Delete succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// GROUP OPERATIONS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncGroupOpsSpec, "IntegrationTests.Nakama.GroupOps", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession MemberSession; + FString UserId; + FString MemberUserId; + FString GroupId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncGroupOpsSpec) + + +void FNakamaAsyncGroupOpsSpec::Define() +{ + BeforeEach([this]() + { + GroupId.Empty(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom MemberAccount; + MemberAccount.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, MemberAccount.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& MemberAuthResult) + { + MemberSession = MemberAuthResult; + return Nakama::GetAccount(ClientConfig, MemberSession); + }) + .Next([this, Done](FNakamaAccountResult MemberAccResult) + { + ASYNC_FAIL_ON_ERROR(MemberAccResult, Done); + MemberUserId = MemberAccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + Nakama::DeleteAccount(ClientConfig, MemberSession) + .Next([this](FNakamaVoidResult) { return Nakama::DeleteAccount(ClientConfig, Session); }) + .Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("JoinGroup", [this]() + { + LatentIt("should join open group", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("JoinGroup_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& CreateResult) + { + GroupId = CreateResult.Id; + return Nakama::JoinGroup(ClientConfig, MemberSession, GroupId); + }) + .Next([this, Done](FNakamaVoidResult JoinResult) + { + ASYNC_FAIL_ON_ERROR(JoinResult, Done); + TestTrue("Joined open group", true); + Done.Execute(); + }); + }); + + LatentIt("should fail to join non-existent group", [this](const FDoneDelegate& Done) + { + Nakama::JoinGroup(ClientConfig, Session, TEXT("00000000-0000-0000-0000-000000000000")) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for non-existent group", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("LeaveGroup", [this]() + { + LatentIt("should leave group", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("LeaveGroup_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& CreateResult) + { + GroupId = CreateResult.Id; + return Nakama::JoinGroup(ClientConfig, MemberSession, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + return Nakama::LeaveGroup(ClientConfig, MemberSession, GroupId); + }) + .Next([this, Done](FNakamaVoidResult LeaveResult) + { + ASYNC_FAIL_ON_ERROR(LeaveResult, Done); + TestTrue("Left group", true); + Done.Execute(); + }); + }); + }); + + Describe("UpdateGroup", [this]() + { + LatentIt("should update group name", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("UpdateGroup_%s"), *GenerateShortId()); + FString NewGroupName = FString::Printf(TEXT("Updated_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this, NewGroupName](const FNakamaGroup& CreateResult) + { + return Nakama::UpdateGroup(ClientConfig, Session, CreateResult.Id, NewGroupName, TEXT("Updated description"), TEXT(""), TEXT("en"), true); + }) + .Next([this, Done](FNakamaVoidResult UpdateResult) + { + ASYNC_FAIL_ON_ERROR(UpdateResult, Done); + TestTrue("Group updated", true); + Done.Execute(); + }); + }); + }); + + Describe("DeleteGroup", [this]() + { + LatentIt("should delete group as superadmin", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("DeleteGroup_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& CreateResult) + { + return Nakama::DeleteGroup(ClientConfig, Session, CreateResult.Id); + }) + .Next([this, Done](FNakamaVoidResult DeleteResult) + { + ASYNC_FAIL_ON_ERROR(DeleteResult, Done); + TestTrue("Group deleted", true); + Done.Execute(); + }); + }); + }); + + Describe("ListGroupUsers", [this]() + { + LatentIt("should list group users", [this](const FDoneDelegate& Done) + { + FString GroupName = FString::Printf(TEXT("ListUsers_%s"), *GenerateShortId()); + + Nakama::CreateGroup(ClientConfig, Session, GroupName, TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& CreateResult) + { + return Nakama::ListGroupUsers(ClientConfig, Session, CreateResult.Id, 100, 0, TEXT("")); + }) + .Next([this, Done](FNakamaGroupUserListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + TestTrue("Has at least creator", ListResult.Value.GroupUsers.Num() >= 1); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// RPC TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncRpcSpec, "IntegrationTests.Nakama.RPC", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncRpcSpec) + + +void FNakamaAsyncRpcSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("RpcFunc", [this]() + { + LatentIt("should fail with empty RPC ID", [this](const FDoneDelegate& Done) + { + Nakama::RpcFunc(ClientConfig, Session, TEXT(""), nullptr, TEXT("")) + .Next([this, Done](FNakamaRpcResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty RPC ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with non-existent RPC", [this](const FDoneDelegate& Done) + { + Nakama::RpcFunc(ClientConfig, Session, TEXT("nonexistent_rpc_function"), nullptr, TEXT("")) + .Next([this, Done](FNakamaRpcResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for non-existent RPC", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// AUTH EXTENDED TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAuthExtSpec, "IntegrationTests.Nakama.AuthExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncAuthExtSpec) + + +void FNakamaAsyncAuthExtSpec::Define() +{ + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("CustomAuthExtended", [this]() + { + LatentIt("should fail with create=false for non-existent user", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, false, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got NOT_FOUND error", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should authenticate existing user with create=false", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this, Account](const FNakamaSession&) + { + return Nakama::AuthenticateCustom(ClientConfig, Account.Id, false, TEXT("")); + }) + .Next([this, Done](FNakamaSessionResult SecondResult) + { + ASYNC_FAIL_ON_ERROR(SecondResult, Done); + TestTrue("Session has token", !SecondResult.Value.Token.IsEmpty()); + Session = SecondResult.Value; + Done.Execute(); + }); + }); + }); + + Describe("DeviceAuthExtended", [this]() + { + LatentIt("should fail with device ID too long", [this](const FDoneDelegate& Done) + { + FNakamaAccountDevice Account; + Account.Id = FString::ChrN(150, 'a'); + + Nakama::AuthenticateDevice(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for long device ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("FacebookAuth", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountFacebook Account; + Account.Token = TEXT(""); + + Nakama::AuthenticateFacebook(ClientConfig, Account.Token, true, TEXT(""), false).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("GoogleAuth", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountGoogle Account; + Account.Token = TEXT(""); + + Nakama::AuthenticateGoogle(ClientConfig, Account.Token, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("SteamAuth", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountSteam Account; + Account.Token = TEXT(""); + + Nakama::AuthenticateSteam(ClientConfig, Account.Token, true, TEXT(""), false).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("AppleAuth", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountApple Account; + Account.Token = TEXT(""); + + Nakama::AuthenticateApple(ClientConfig, Account.Token, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// DELETE FRIENDS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncDeleteFriendsSpec, "IntegrationTests.Nakama.DeleteFriends", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession FriendSession; + FString UserId; + FString FriendUserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncDeleteFriendsSpec) + + +void FNakamaAsyncDeleteFriendsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom FriendAccount; + FriendAccount.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, FriendAccount.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& FriendAuthResult) + { + FriendSession = FriendAuthResult; + return Nakama::GetAccount(ClientConfig, FriendSession); + }) + .Next([this, Done](FNakamaAccountResult FriendAccResult) + { + ASYNC_FAIL_ON_ERROR(FriendAccResult, Done); + FriendUserId = FriendAccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + auto DeleteMain = [this, Done]() + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }; + + if (FriendSession.Token.IsEmpty()) { DeleteMain(); return; } + Nakama::DeleteAccount(ClientConfig, FriendSession).Next([DeleteMain](FNakamaVoidResult) { DeleteMain(); }); + }); + + + + Describe("DeleteFriends", [this]() + { + LatentIt("should delete friend by ID", [this](const FDoneDelegate& Done) + { + TArray AddIds; + AddIds.Add(FriendUserId); + + Nakama::AddFriends(ClientConfig, Session, AddIds, TArray(), TEXT("")) + .Next([this](const FNakamaVoid&) + { + TArray DeleteIds; + DeleteIds.Add(FriendUserId); + return Nakama::DeleteFriends(ClientConfig, Session, DeleteIds, TArray()); + }) + .Next([this, Done](FNakamaVoidResult DeleteResult) + { + ASYNC_FAIL_ON_ERROR(DeleteResult, Done); + TestTrue("Friend deleted", true); + Done.Execute(); + }); + }); + + LatentIt("should succeed when deleting non-friend", [this](const FDoneDelegate& Done) + { + TArray DeleteIds; + DeleteIds.Add(FriendUserId); + + Nakama::DeleteFriends(ClientConfig, Session, DeleteIds, TArray()).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Delete non-friend succeeded (no-op)", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// IMPORT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncImportSpec, "IntegrationTests.Nakama.Import", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncImportSpec) + + +void FNakamaAsyncImportSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ImportSteamFriends", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountSteam Account; + Account.Token = TEXT(""); + + Nakama::ImportSteamFriends(ClientConfig, Session, Account.Token, false) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ImportFacebookFriends", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountFacebook Account; + Account.Token = TEXT(""); + + Nakama::ImportFacebookFriends(ClientConfig, Session, Account.Token, false) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL ACCOUNT TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAccountExtSpec, "IntegrationTests.Nakama.AccountExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncAccountExtSpec) + + +void FNakamaAsyncAccountExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("UpdateAccountExtended", [this]() + { + LatentIt("should update all fields", [this](const FDoneDelegate& Done) + { + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), FString::Printf(TEXT("Display_%s"), *GenerateShortId()), TEXT("https://example.com/avatar.png"), TEXT("es"), TEXT("US"), TEXT("America/New_York")) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Update all fields succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should update lang tag", [this](const FDoneDelegate& Done) + { + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), TEXT(""), TEXT(""), TEXT("fr"), TEXT(""), TEXT("")) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + TestEqual("Lang tag updated", AccResult.Value.User.LangTag, TEXT("fr")); + Done.Execute(); + }); + }); + + LatentIt("should update location", [this](const FDoneDelegate& Done) + { + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), TEXT(""), TEXT(""), TEXT(""), TEXT("London"), TEXT("")) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + TestEqual("Location updated", AccResult.Value.User.Location, TEXT("London")); + Done.Execute(); + }); + }); + + LatentIt("should update timezone", [this](const FDoneDelegate& Done) + { + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), TEXT(""), TEXT(""), TEXT(""), TEXT(""), TEXT("Europe/London")) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + TestEqual("Timezone updated", AccResult.Value.User.Timezone, TEXT("Europe/London")); + Done.Execute(); + }); + }); + + LatentIt("should update avatar URL", [this](const FDoneDelegate& Done) + { + FString NewAvatarUrl = TEXT("https://example.com/new-avatar.png"); + Nakama::UpdateAccount(ClientConfig, Session, TEXT(""), TEXT(""), NewAvatarUrl, TEXT(""), TEXT(""), TEXT("")) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done, NewAvatarUrl](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + TestEqual("Avatar URL updated", AccResult.Value.User.AvatarUrl, NewAvatarUrl); + Done.Execute(); + }); + }); + }); + + Describe("GetAccountDetails", [this]() + { + LatentIt("should return account with devices", [this](const FDoneDelegate& Done) + { + Nakama::GetAccount(ClientConfig, Session).Next([this, Done](FNakamaAccountResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Account has user", !Result.Value.User.Id.IsEmpty()); + TestTrue("Account has create time", !Result.Value.User.CreateTime.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL FRIENDS TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncFriendsExtSpec, "IntegrationTests.Nakama.FriendsExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession FriendSession; + FString UserId; + FString FriendUserId; + FString FriendUsername; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncFriendsExtSpec) + + +void FNakamaAsyncFriendsExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + FriendUsername = FString::Printf(TEXT("friend_%s"), *GenerateShortId()); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom FriendAccount; + FriendAccount.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, FriendAccount.Id, true, FriendUsername); + }) + .Next([this](const FNakamaSession& FriendResult) + { + FriendSession = FriendResult; + return Nakama::GetAccount(ClientConfig, FriendSession); + }) + .Next([this, Done](FNakamaAccountResult FriendAccResult) + { + ASYNC_FAIL_ON_ERROR(FriendAccResult, Done); + FriendUserId = FriendAccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + auto DeleteMain = [this, Done]() + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }; + + if (FriendSession.Token.IsEmpty()) { DeleteMain(); return; } + Nakama::DeleteAccount(ClientConfig, FriendSession).Next([DeleteMain](FNakamaVoidResult) { DeleteMain(); }); + }); + + + + Describe("ListFriendsWithFilters", [this]() + { + LatentIt("should list friends with limit", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 5, 0, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List friends with limit succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should list friends with state filter", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 100, 0, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List friends by state succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should list blocked friends", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 100, 3, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List blocked friends succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("AddFriendsByUsername", [this]() + { + LatentIt("should add friend by username", [this](const FDoneDelegate& Done) + { + TArray Usernames; + Usernames.Add(FriendUsername); + Nakama::AddFriends(ClientConfig, Session, TArray(), Usernames, TEXT("")).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("AddFriends by username succeeded", true); + Done.Execute(); + }); + }); + + LatentIt("should handle invalid user ID format gracefully", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(TEXT("not-a-valid-uuid")); + Nakama::AddFriends(ClientConfig, Session, Ids, TArray(), TEXT("")).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Server handled invalid UUID", true); + Done.Execute(); + }); + }); + }); + + Describe("DeleteFriendsByUsername", [this]() + { + LatentIt("should delete friend by username", [this](const FDoneDelegate& Done) + { + TArray AddUsernames; + AddUsernames.Add(FriendUsername); + Nakama::AddFriends(ClientConfig, Session, TArray(), AddUsernames, TEXT("")) + .Next([this](const FNakamaVoid&) + { + TArray DeleteUsernames; + DeleteUsernames.Add(FriendUsername); + return Nakama::DeleteFriends(ClientConfig, Session, TArray(), DeleteUsernames); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Delete friend by username succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("BlockFriendsByUsername", [this]() + { + LatentIt("should block user by username", [this](const FDoneDelegate& Done) + { + TArray Usernames; + Usernames.Add(FriendUsername); + Nakama::BlockFriends(ClientConfig, Session, TArray(), Usernames).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Block by username succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL STORAGE TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncStorageExtSpec, "IntegrationTests.Nakama.StorageExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncStorageExtSpec) + + +void FNakamaAsyncStorageExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("WriteStorageMultiple", [this]() + { + LatentIt("should write multiple storage objects", [this](const FDoneDelegate& Done) + { + TArray Objects; + for (int32 i = 0; i < 5; i++) + { + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("multi_collection"); + Object.Key = FString::Printf(TEXT("multi_key_%d_%s"), i, *GenerateShortId()); + Object.Value = FString::Printf(TEXT("{\"index\": %d}"), i); + Object.PermissionRead = 1; + Object.PermissionWrite = 1; + Objects.Add(Object); + } + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestEqual("Got 5 acks", Result.Value.Acks.Num(), 5); + Done.Execute(); + }); + }); + }); + + Describe("WriteStorageValidation", [this]() + { + LatentIt("should fail with empty collection", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Object; + Object.Collection = TEXT(""); + Object.Key = TEXT("test_key"); + Object.Value = TEXT("{\"test\": true}"); + Objects.Add(Object); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty collection", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with empty key", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("test_collection"); + Object.Key = TEXT(""); + Object.Value = TEXT("{\"test\": true}"); + Objects.Add(Object); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty key", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should write with public read permission", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("public_collection"); + Object.Key = FString::Printf(TEXT("public_key_%s"), *GenerateShortId()); + Object.Value = TEXT("{\"public\": true}"); + Object.PermissionRead = 2; + Object.PermissionWrite = 1; + Objects.Add(Object); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Write with public permission succeeded", Result.Value.Acks.Num() > 0); + Done.Execute(); + }); + }); + + LatentIt("should fail with JSON array as value", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("test_collection"); + Object.Key = TEXT("array_key"); + Object.Value = TEXT("[1, 2, 3]"); + Objects.Add(Object); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for JSON array", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ReadStorageValidation", [this]() + { + LatentIt("should return empty for non-existent key", [this](const FDoneDelegate& Done) + { + TArray ReadIds; + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("nonexistent_collection"); + ReadId.Key = TEXT("nonexistent_key"); + ReadId.UserId = UserId; + ReadIds.Add(ReadId); + Nakama::ReadStorageObjects(ClientConfig, Session, ReadIds).Next([this, Done](FNakamaStorageObjectsResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Empty result for non-existent key", Result.Value.Objects.Num() == 0); + Done.Execute(); + }); + }); + + LatentIt("should read multiple objects", [this](const FDoneDelegate& Done) + { + TArray WriteObjects; + TArray Keys; + for (int32 i = 0; i < 3; i++) + { + FString Key = FString::Printf(TEXT("read_multi_%d_%s"), i, *GenerateShortId()); + Keys.Add(Key); + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("read_multi_collection"); + Object.Key = Key; + Object.Value = FString::Printf(TEXT("{\"index\": %d}"), i); + Object.PermissionRead = 1; + Object.PermissionWrite = 1; + WriteObjects.Add(Object); + } + Nakama::WriteStorageObjects(ClientConfig, Session, WriteObjects) + .Next([this, Keys](const FNakamaStorageObjectAcks&) + { + TArray ReadIds; + for (const FString& Key : Keys) + { + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("read_multi_collection"); + ReadId.Key = Key; + ReadId.UserId = UserId; + ReadIds.Add(ReadId); + } + return Nakama::ReadStorageObjects(ClientConfig, Session, ReadIds); + }) + .Next([this, Done](FNakamaStorageObjectsResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestEqual("Read 3 objects", Result.Value.Objects.Num(), 3); + Done.Execute(); + }); + }); + }); + + Describe("ListStorageValidation", [this]() + { + LatentIt("should list with small limit", [this](const FDoneDelegate& Done) + { + TArray Objects; + for (int32 i = 0; i < 5; i++) + { + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("list_limit_collection"); + Object.Key = FString::Printf(TEXT("limit_key_%d_%s"), i, *GenerateShortId()); + Object.Value = FString::Printf(TEXT("{\"index\": %d}"), i); + Object.PermissionRead = 1; + Object.PermissionWrite = 1; + Objects.Add(Object); + } + Nakama::WriteStorageObjects(ClientConfig, Session, Objects) + .Next([this](const FNakamaStorageObjectAcks&) + { + return Nakama::ListStorageObjects(ClientConfig, Session, UserId, TEXT("list_limit_collection"), 2, TEXT("")); + }) + .Next([this, Done](FNakamaStorageObjectListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + TestTrue("List respects limit", ListResult.Value.Objects.Num() <= 2); + Done.Execute(); + }); + }); + }); + + Describe("DeleteStorageValidation", [this]() + { + LatentIt("should delete multiple objects", [this](const FDoneDelegate& Done) + { + TArray WriteObjects; + TArray Keys; + for (int32 i = 0; i < 3; i++) + { + FString Key = FString::Printf(TEXT("delete_multi_%d_%s"), i, *GenerateShortId()); + Keys.Add(Key); + FNakamaWriteStorageObject Object; + Object.Collection = TEXT("delete_multi_collection"); + Object.Key = Key; + Object.Value = FString::Printf(TEXT("{\"index\": %d}"), i); + Object.PermissionRead = 1; + Object.PermissionWrite = 1; + WriteObjects.Add(Object); + } + Nakama::WriteStorageObjects(ClientConfig, Session, WriteObjects) + .Next([this, Keys](const FNakamaStorageObjectAcks&) + { + TArray DeleteIds; + for (const FString& Key : Keys) + { + FNakamaDeleteStorageObjectId DeleteId; + DeleteId.Collection = TEXT("delete_multi_collection"); + DeleteId.Key = Key; + DeleteIds.Add(DeleteId); + } + return Nakama::DeleteStorageObjects(ClientConfig, Session, DeleteIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Delete multiple objects succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL GROUP TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncGroupExtSpec, "IntegrationTests.Nakama.GroupExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession MemberSession; + FString UserId; + FString MemberUserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncGroupExtSpec) + + +void FNakamaAsyncGroupExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom MemberAccount; + MemberAccount.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, MemberAccount.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& MemberResult) + { + MemberSession = MemberResult; + return Nakama::GetAccount(ClientConfig, MemberSession); + }) + .Next([this, Done](FNakamaAccountResult MemberAccResult) + { + ASYNC_FAIL_ON_ERROR(MemberAccResult, Done); + MemberUserId = MemberAccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + Nakama::DeleteAccount(ClientConfig, MemberSession) + .Next([this](FNakamaVoidResult) { return Nakama::DeleteAccount(ClientConfig, Session); }) + .Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("CreateGroupValidation", [this]() + { + LatentIt("should create closed group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("ClosedGroup_%s"), *GenerateShortId()), TEXT("Closed group for testing"), TEXT(""), TEXT("en"), false, 100) + .Next([this, Done](FNakamaGroupResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Group created", !Result.Value.Id.IsEmpty()); + TestFalse("Group is closed", Result.Value.Open); + Done.Execute(); + }); + }); + + LatentIt("should create group with description and lang tag", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("MetaGroup_%s"), *GenerateShortId()), TEXT("Group with description"), TEXT(""), TEXT("es"), true, 100) + .Next([this, Done](FNakamaGroupResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Group created with description", !Result.Value.Id.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ListGroupsWithFilters", [this]() + { + LatentIt("should list groups by name prefix", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("SearchGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup&) + { + return Nakama::ListGroups(ClientConfig, Session, TEXT("SearchGroup"), TEXT(""), 100, TEXT(""), 0, false); + }) + .Next([this, Done](FNakamaGroupListResult Result) + { + if (Result.bIsError) { TestTrue("Server handled name filter", true); Done.Execute(); return; } + TestTrue("Found groups by name", Result.Value.Groups.Num() >= 1); + Done.Execute(); + }); + }); + + LatentIt("should list groups with limit", [this](const FDoneDelegate& Done) + { + Nakama::ListGroups(ClientConfig, Session, TEXT(""), TEXT(""), 5, TEXT(""), 0, true).Next([this, Done](FNakamaGroupListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List respects limit", Result.Value.Groups.Num() <= 5); + Done.Execute(); + }); + }); + }); + + Describe("GroupMemberOperations", [this]() + { + LatentIt("should add user to group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("AddUserGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + TArray UserIds; + UserIds.Add(MemberUserId); + return Nakama::AddGroupUsers(ClientConfig, Session, GroupResult.Id, UserIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Added user to group", true); + Done.Execute(); + }); + }); + + LatentIt("should kick user from group", [this](const FDoneDelegate& Done) + { + auto GId = MakeShared(); + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("KickGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this, GId](const FNakamaGroup& GroupResult) + { + *GId = GroupResult.Id; + TArray UserIds; + UserIds.Add(MemberUserId); + return Nakama::AddGroupUsers(ClientConfig, Session, *GId, UserIds); + }) + .Next([this, GId](const FNakamaVoid&) + { + TArray KickIds; + KickIds.Add(MemberUserId); + return Nakama::KickGroupUsers(ClientConfig, Session, *GId, KickIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Kicked user from group", true); + Done.Execute(); + }); + }); + + LatentIt("should ban user from group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("BanGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + TArray UserIds; + UserIds.Add(MemberUserId); + return Nakama::BanGroupUsers(ClientConfig, Session, GroupResult.Id, UserIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Banned user from group", true); + Done.Execute(); + }); + }); + + LatentIt("should promote user in group", [this](const FDoneDelegate& Done) + { + auto GId = MakeShared(); + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("PromoteGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this, GId](const FNakamaGroup& GroupResult) + { + *GId = GroupResult.Id; + TArray UserIds; + UserIds.Add(MemberUserId); + return Nakama::AddGroupUsers(ClientConfig, Session, *GId, UserIds); + }) + .Next([this, GId](const FNakamaVoid&) + { + TArray PromoteIds; + PromoteIds.Add(MemberUserId); + return Nakama::PromoteGroupUsers(ClientConfig, Session, *GId, PromoteIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Promoted user in group", true); + Done.Execute(); + }); + }); + + LatentIt("should demote user in group", [this](const FDoneDelegate& Done) + { + auto GId = MakeShared(); + FString TargetUserId = MemberUserId; + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("DemoteGroup_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this, GId, TargetUserId](const FNakamaGroup& GroupResult) + { + *GId = GroupResult.Id; + TArray UserIds; + UserIds.Add(TargetUserId); + return Nakama::AddGroupUsers(ClientConfig, Session, *GId, UserIds); + }) + .Next([this, GId, TargetUserId](const FNakamaVoid&) + { + TArray PromoteIds; + PromoteIds.Add(TargetUserId); + return Nakama::PromoteGroupUsers(ClientConfig, Session, *GId, PromoteIds); + }) + .Next([this, GId, TargetUserId](const FNakamaVoid&) + { + TArray DemoteIds; + DemoteIds.Add(TargetUserId); + return Nakama::DemoteGroupUsers(ClientConfig, Session, *GId, DemoteIds); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Demoted user in group", true); + Done.Execute(); + }); + }); + }); + + Describe("ListGroupUsersWithState", [this]() + { + LatentIt("should list group users with state filter", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("ListUsersState_%s"), *GenerateShortId()), TEXT("Test"), TEXT(""), TEXT("en"), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + return Nakama::ListGroupUsers(ClientConfig, Session, GroupResult.Id, 100, 0, TEXT("")); + }) + .Next([this, Done](FNakamaGroupUserListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Found superadmin (creator)", Result.Value.GroupUsers.Num() >= 1); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL UNLINK TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncUnlinkSpec, "IntegrationTests.Nakama.Unlink", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString DeviceId; + FString CustomId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncUnlinkSpec) + + +void FNakamaAsyncUnlinkSpec::Define() +{ + BeforeEach([this]() + { + DeviceId = GenerateId(); + CustomId = GenerateId(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountDevice Account; + Account.Id = DeviceId; + Nakama::AuthenticateDevice(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("UnlinkCustom", [this]() + { + LatentIt("should unlink custom ID", [this](const FDoneDelegate& Done) + { + Nakama::LinkCustom(ClientConfig, Session, CustomId, TMap()) + .Next([this](const FNakamaVoid&) + { + return Nakama::UnlinkCustom(ClientConfig, Session, CustomId, TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Unlink custom succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("UnlinkEmail", [this]() + { + LatentIt("should unlink email", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("unlink_%s@example.com"), *GenerateShortId()); + Nakama::LinkEmail(ClientConfig, Session, Email, TEXT("password123"), TMap()) + .Next([this, Email](const FNakamaVoid&) + { + return Nakama::UnlinkEmail(ClientConfig, Session, Email, TEXT("password123"), TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Unlink email succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("UnlinkDevice", [this]() + { + LatentIt("should unlink device", [this](const FDoneDelegate& Done) + { + FString NewDeviceId = GenerateId(); + Nakama::LinkDevice(ClientConfig, Session, NewDeviceId, TMap()) + .Next([this, NewDeviceId](const FNakamaVoid&) + { + return Nakama::UnlinkDevice(ClientConfig, Session, NewDeviceId, TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Unlink device succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL MATCHES TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncMatchesExtSpec, "IntegrationTests.Nakama.MatchesExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncMatchesExtSpec) + + +void FNakamaAsyncMatchesExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListMatchesWithFilters", [this]() + { + LatentIt("should list matches with min size filter", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 100, false, TEXT(""), 2, 100, TEXT("")).Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + for (const auto& Match : Result.Value.Matches) { TestTrue("Match has min size", Match.Size >= 2); } + Done.Execute(); + }); + }); + + LatentIt("should list matches with max size filter", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 100, false, TEXT(""), 0, 4, TEXT("")).Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + for (const auto& Match : Result.Value.Matches) { TestTrue("Match has max size", Match.Size <= 4); } + Done.Execute(); + }); + }); + + LatentIt("should list matches with label filter", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 10, true, TEXT("test_label"), 0, 100, TEXT("")).Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List with label filter succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// CHANNEL MESSAGES TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncChannelSpec, "IntegrationTests.Nakama.Channel", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncChannelSpec) + + +void FNakamaAsyncChannelSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListChannelMessages", [this]() + { + LatentIt("should fail with empty channel ID", [this](const FDoneDelegate& Done) + { + Nakama::ListChannelMessages(ClientConfig, Session, TEXT(""), 100, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty channel ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should list room channel messages", [this](const FDoneDelegate& Done) + { + FString ChannelId = FString::Printf(TEXT("2.testroom_%s"), *GenerateShortId()); + Nakama::ListChannelMessages(ClientConfig, Session, ChannelId, 100, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + + LatentIt("should list messages with limit", [this](const FDoneDelegate& Done) + { + FString ChannelId = FString::Printf(TEXT("2.limitroom_%s"), *GenerateShortId()); + Nakama::ListChannelMessages(ClientConfig, Session, ChannelId, 5, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + if (!Result.bIsError) { TestTrue("List respects limit", Result.Value.Messages.Num() <= 5); } + else { TestTrue("Got expected error or success", true); } + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL NOTIFICATION TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncNotificationsExtSpec, "IntegrationTests.Nakama.NotificationsExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncNotificationsExtSpec) + + +void FNakamaAsyncNotificationsExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListNotificationsWithFilters", [this]() + { + LatentIt("should list notifications with limit", [this](const FDoneDelegate& Done) + { + Nakama::ListNotifications(ClientConfig, Session, 5, TEXT("")).Next([this, Done](FNakamaNotificationListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List respects limit", Result.Value.Notifications.Num() <= 5); + Done.Execute(); + }); + }); + }); + + Describe("DeleteNotifications", [this]() + { + LatentIt("should handle invalid notification ID gracefully", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(TEXT("not-a-valid-uuid")); + Nakama::DeleteNotifications(ClientConfig, Session, Ids).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Server handled invalid UUID", true); + Done.Execute(); + }); + }); + + LatentIt("should succeed when deleting non-existent notification", [this](const FDoneDelegate& Done) + { + TArray Ids; + Ids.Add(TEXT("ffffffff-ffff-ffff-ffff-ffffffffffff")); + Nakama::DeleteNotifications(ClientConfig, Session, Ids).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Delete non-existent notification handled", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// ADDITIONAL AUTH VALIDATION TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAuthValidationSpec, "IntegrationTests.Nakama.Auth.Validation", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncAuthValidationSpec) + + +void FNakamaAsyncAuthValidationSpec::Define() +{ + + + Describe("CustomAuth.Validation", [this]() + { + LatentIt("should fail with custom ID containing spaces", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = TEXT("test id with spaces"); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for ID with spaces", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with username too long", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, FString::ChrN(150, 'x')).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for username too long", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should accept username with special chars", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + FString Username = TEXT("u@$^!~+"); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, Username).Next([this, Done](FNakamaSessionResult Result) + { + if (Result.bIsError) + { + AddError(FString::Printf(TEXT("Unexpected error %d: %s"), Result.Error.Code, *Result.Error.Message)); + Done.Execute(); + return; + } + TestTrue("Session is valid", !Result.Value.Token.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) + { + Done.Execute(); + }); + }); + }); + }); + + Describe("DeviceAuth.Validation", [this]() + { + LatentIt("should fail with device ID containing only spaces", [this](const FDoneDelegate& Done) + { + FNakamaAccountDevice Account; + Account.Id = TEXT(" "); + Nakama::AuthenticateDevice(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for spaces-only ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("EmailAuth.Validation", [this]() + { + LatentIt("should fail with email too short", [this](const FDoneDelegate& Done) + { + FNakamaAccountEmail Account; + Account.Email = TEXT("a@b.c"); + Account.Password = TEXT("password123"); + Nakama::AuthenticateEmail(ClientConfig, Account.Email, Account.Password, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for email too short", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with missing at symbol in email", [this](const FDoneDelegate& Done) + { + FNakamaAccountEmail Account; + Account.Email = TEXT("notanemailaddress.com"); + Account.Password = TEXT("password123"); + Nakama::AuthenticateEmail(ClientConfig, Account.Email, Account.Password, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for missing @ in email", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with empty password", [this](const FDoneDelegate& Done) + { + FNakamaAccountEmail Account; + Account.Email = FString::Printf(TEXT("test_%s@example.com"), *GenerateShortId()); + Account.Password = TEXT(""); + Nakama::AuthenticateEmail(ClientConfig, Account.Email, Account.Password, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty password", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should authenticate existing user with create=false", [this](const FDoneDelegate& Done) + { + FNakamaAccountEmail Account; + Account.Email = FString::Printf(TEXT("existing_%s@example.com"), *GenerateShortId()); + Account.Password = TEXT("password123"); + Nakama::AuthenticateEmail(ClientConfig, Account.Email, Account.Password, true, TEXT("")) + .Next([this, Account](const FNakamaSession&) + { + return Nakama::AuthenticateEmail(ClientConfig, Account.Email, Account.Password, false, TEXT("")); + }) + .Next([this, Done](FNakamaSessionResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + Nakama::DeleteAccount(ClientConfig, Result.Value).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + }); + }); +} + +// ============================================================================ +// ACCOUNT DELETE TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncAccountDeleteSpec, "IntegrationTests.Nakama.Account.Delete", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncAccountDeleteSpec) + + +void FNakamaAsyncAccountDeleteSpec::Define() +{ + + + Describe("DeleteAccount", [this]() + { + LatentIt("should delete account successfully", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::DeleteAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Account deleted successfully", true); + Done.Execute(); + }); + }); + + LatentIt("should fail to get account after deletion", [this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::DeleteAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got account after deletion")); Done.Execute(); return; } + TestTrue("Got error when accessing deleted account", + Result.Error.Code == ENakamaErrorCode::Unauthenticated || + Result.Error.Code == ENakamaErrorCode::NotFound || + !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// SOCIAL AUTH VALIDATION TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncSocialAuthSpec, "IntegrationTests.Nakama.Auth.Social", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + + +END_DEFINE_SPEC(FNakamaAsyncSocialAuthSpec) + + +void FNakamaAsyncSocialAuthSpec::Define() +{ + + + Describe("FacebookAuth.Validation", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountFacebook Account; + Account.Token = TEXT(""); + Nakama::AuthenticateFacebook(ClientConfig, Account.Token, true, TEXT(""), true).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty Facebook token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("GoogleAuth.Validation", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountGoogle Account; + Account.Token = TEXT(""); + Nakama::AuthenticateGoogle(ClientConfig, Account.Token, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty Google token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("SteamAuth.Validation", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountSteam Account; + Account.Token = TEXT(""); + Nakama::AuthenticateSteam(ClientConfig, Account.Token, true, TEXT(""), true).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty Steam token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("AppleAuth.Validation", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + FNakamaAccountApple Account; + Account.Token = TEXT(""); + Nakama::AuthenticateApple(ClientConfig, Account.Token, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty Apple token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("GameCenterAuth.Validation", [this]() + { + LatentIt("should fail with missing required fields", [this](const FDoneDelegate& Done) + { + FNakamaAccountGameCenter Account; + Account.PlayerId = TEXT(""); + Account.BundleId = TEXT(""); + Account.TimestampSeconds = 0; + Account.Salt = TEXT(""); + Account.Signature = TEXT(""); + Account.PublicKeyUrl = TEXT(""); + Nakama::AuthenticateGameCenter(ClientConfig, Account.PlayerId, Account.BundleId, Account.TimestampSeconds, Account.Salt, Account.Signature, Account.PublicKeyUrl, true, TEXT("")).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for missing GameCenter fields", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// SESSION EXTENDED TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncSessionExtSpec, "IntegrationTests.Nakama.SessionExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncSessionExtSpec) + + +void FNakamaAsyncSessionExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("Logout.Extended", [this]() + { + LatentIt("should logout and invalidate session", [this](const FDoneDelegate& Done) + { + Nakama::SessionLogout(ClientConfig, Session, Session.Token, Session.RefreshToken) + .Next([this](const FNakamaVoid&) + { + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult Result) + { + TestTrue("Logout succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("SessionRefresh.Extended", [this]() + { + LatentIt("should fail with empty token", [this](const FDoneDelegate& Done) + { + Nakama::SessionRefresh(ClientConfig, TEXT(""), TMap()).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty refresh token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with malformed token", [this](const FDoneDelegate& Done) + { + Nakama::SessionRefresh(ClientConfig, TEXT("not-a-valid-jwt-token"), TMap()).Next([this, Done](FNakamaSessionResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for malformed token", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// USERS EXTENDED TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncUsersExtSpec, "IntegrationTests.Nakama.UsersExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncUsersExtSpec) + + +void FNakamaAsyncUsersExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("GetUsers.Extended", [this]() + { + LatentIt("should return empty for empty request", [this](const FDoneDelegate& Done) + { + Nakama::GetUsers(ClientConfig, Session, TArray(), TArray(), TArray()).Next([this, Done](FNakamaUsersResult Result) + { + if (!Result.bIsError) { TestTrue("Empty request returns empty result", Result.Value.Users.Num() == 0); } + else { TestTrue("Got expected response", true); } + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid UUID format", [this](const FDoneDelegate& Done) + { + TArray InvalidIds; + InvalidIds.Add(TEXT("not-a-valid-uuid")); + Nakama::GetUsers(ClientConfig, Session, InvalidIds, TArray(), TArray()).Next([this, Done](FNakamaUsersResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid UUID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should get user by Facebook ID (returns empty if not linked)", [this](const FDoneDelegate& Done) + { + TArray FacebookIds; + FacebookIds.Add(TEXT("123456789")); + Nakama::GetUsers(ClientConfig, Session, TArray(), TArray(), FacebookIds).Next([this, Done](FNakamaUsersResult Result) + { + if (!Result.bIsError) { TestTrue("Facebook ID search returns empty", Result.Value.Users.Num() == 0); } + else { TestTrue("Got expected response", true); } + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// FRIENDS OF FRIENDS TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncFriendsOfFriendsSpec, "IntegrationTests.Nakama.FriendsOfFriends", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncFriendsOfFriendsSpec) + + +void FNakamaAsyncFriendsOfFriendsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this, Done](FNakamaAccountResult AccResult) + { + ASYNC_FAIL_ON_ERROR(AccResult, Done); + UserId = AccResult.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListFriendsOfFriends", [this]() + { + LatentIt("should list friends of friends", [this](const FDoneDelegate& Done) + { + Nakama::ListFriendsOfFriends(ClientConfig, Session, 100, TEXT("")).Next([this, Done](FNakamaFriendsOfFriendsListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("should respect limit parameter", [this](const FDoneDelegate& Done) + { + Nakama::ListFriendsOfFriends(ClientConfig, Session, 5, TEXT("")).Next([this, Done](FNakamaFriendsOfFriendsListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Result respects limit", Result.Value.FriendsOfFriends.Num() <= 5); + Done.Execute(); + }); + }); + }); + + Describe("ListFriends.Validation", [this]() + { + LatentIt("should accept limit zero", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 0, 0, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("ListFriends with limit=0 accepted", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid state filter", [this](const FDoneDelegate& Done) + { + Nakama::ListFriends(ClientConfig, Session, 100, 99, TEXT("")).Next([this, Done](FNakamaFriendListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid state", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("AddFriends.Validation", [this]() + { + LatentIt("should accept empty IDs and usernames as no-op", [this](const FDoneDelegate& Done) + { + Nakama::AddFriends(ClientConfig, Session, TArray(), TArray(), TEXT("")).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("AddFriends with empty arrays accepted (no-op)", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid UUID", [this](const FDoneDelegate& Done) + { + TArray InvalidIds; + InvalidIds.Add(TEXT("not-a-uuid")); + Nakama::AddFriends(ClientConfig, Session, InvalidIds, TArray(), TEXT("")).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid UUID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("BlockFriends.Validation", [this]() + { + LatentIt("should fail when blocking self", [this](const FDoneDelegate& Done) + { + TArray SelfId; + SelfId.Add(UserId); + Nakama::BlockFriends(ClientConfig, Session, SelfId, TArray()).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for blocking self", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// GROUPS PERMISSIONS AND EDGE CASES TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncGroupPermissionsSpec, "IntegrationTests.Nakama.GroupPermissions", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession Session2; + FString UserId; + FString UserId2; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncGroupPermissionsSpec) + + +void FNakamaAsyncGroupPermissionsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account1; + Account1.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account1.Id, true, TEXT("")) + .Next([this](const FNakamaSession& Result) + { + Session = Result; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& Result2) + { + Session2 = Result2; + return Nakama::GetAccount(ClientConfig, Session2); + }) + .Next([this, Done](FNakamaAccountResult AccResult2) + { + ASYNC_FAIL_ON_ERROR(AccResult2, Done); + UserId2 = AccResult2.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + Nakama::DeleteAccount(ClientConfig, Session) + .Next([this](FNakamaVoidResult) -> TNakamaFuture + { + return Nakama::DeleteAccount(ClientConfig, Session2); + }) + .Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("CreateGroup.Validation", [this]() + { + LatentIt("should accept max count zero", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("testgroup_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 0) + .Next([this, Done](FNakamaGroupResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("CreateGroup with max_count=0 accepted", !Result.Value.Id.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should create closed group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("closedgroup_%s"), *GenerateShortId()), TEXT(""), TEXT("A closed group"), TEXT(""), false, 100) + .Next([this, Done](FNakamaGroupResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Group is closed", !Result.Value.Open); + Done.Execute(); + }); + }); + }); + + Describe("UpdateGroup.Permissions", [this]() + { + LatentIt("should fail with empty group ID", [this](const FDoneDelegate& Done) + { + Nakama::UpdateGroup(ClientConfig, Session, TEXT(""), TEXT("newname"), TEXT(""), TEXT(""), TEXT(""), true).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty group ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid group ID format", [this](const FDoneDelegate& Done) + { + Nakama::UpdateGroup(ClientConfig, Session, TEXT("not-a-valid-uuid"), TEXT("newname"), TEXT(""), TEXT(""), TEXT(""), true).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid group ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("DeleteGroup.Permissions", [this]() + { + LatentIt("should fail with invalid group ID format", [this](const FDoneDelegate& Done) + { + Nakama::DeleteGroup(ClientConfig, Session, TEXT("not-a-valid-uuid")).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid group ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail when non-member tries to delete", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("testdelete_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + return Nakama::DeleteGroup(ClientConfig, Session2, GroupResult.Id); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but non-member could delete")); Done.Execute(); return; } + TestTrue("Non-member cannot delete group", true); + Done.Execute(); + }); + }); + }); + + Describe("JoinGroup.EdgeCases", [this]() + { + LatentIt("should handle joining non-existent group", [this](const FDoneDelegate& Done) + { + Nakama::JoinGroup(ClientConfig, Session, TEXT("ffffffff-ffff-ffff-ffff-ffffffffffff")).Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for non-existent group", Result.Error.Code == ENakamaErrorCode::NotFound || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should handle joining already joined group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("testjoin_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + return Nakama::JoinGroup(ClientConfig, Session, GroupResult.Id); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Join already-joined group handled", true); + Done.Execute(); + }); + }); + }); + + Describe("LeaveGroup.EdgeCases", [this]() + { + LatentIt("should handle leaving non-member group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("testleave_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& GroupResult) + { + return Nakama::LeaveGroup(ClientConfig, Session2, GroupResult.Id); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Leave non-member group handled", true); + Done.Execute(); + }); + }); + }); + + Describe("ListGroupUsers.Validation", [this]() + { + LatentIt("should fail with invalid group ID format", [this](const FDoneDelegate& Done) + { + Nakama::ListGroupUsers(ClientConfig, Session, TEXT("not-a-valid-uuid"), 100, 0, TEXT("")).Next([this, Done](FNakamaGroupUserListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid group ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ListUserGroups.Validation", [this]() + { + LatentIt("should fail with invalid user ID format", [this](const FDoneDelegate& Done) + { + Nakama::ListUserGroups(ClientConfig, Session, TEXT("not-a-valid-uuid"), 100, 0, TEXT("")).Next([this, Done](FNakamaUserGroupListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid user ID", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should list own groups", [this](const FDoneDelegate& Done) + { + Nakama::ListUserGroups(ClientConfig, Session, UserId, 100, 0, TEXT("")).Next([this, Done](FNakamaUserGroupListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("List own groups succeeded", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// STORAGE PERMISSIONS TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncStoragePermissionsSpec, "IntegrationTests.Nakama.StoragePermissions", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession Session2; + FString UserId; + FString UserId2; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncStoragePermissionsSpec) + + +void FNakamaAsyncStoragePermissionsSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account1; + Account1.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account1.Id, true, TEXT("")) + .Next([this](const FNakamaSession& Result) + { + Session = Result; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& Result2) + { + Session2 = Result2; + return Nakama::GetAccount(ClientConfig, Session2); + }) + .Next([this, Done](FNakamaAccountResult AccResult2) + { + ASYNC_FAIL_ON_ERROR(AccResult2, Done); + UserId2 = AccResult2.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + auto DeleteMain = [this, Done]() + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }; + + if (Session2.Token.IsEmpty()) { DeleteMain(); return; } + Nakama::DeleteAccount(ClientConfig, Session2).Next([DeleteMain](FNakamaVoidResult) { DeleteMain(); }); + }); + + + + Describe("WriteStorage.Permissions", [this]() + { + LatentIt("should fail with invalid permission read value", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("test_collection"); + Obj.Key = FString::Printf(TEXT("test_key_%s"), *GenerateShortId()); + Obj.Value = TEXT("{\"data\":\"test\"}"); + Obj.PermissionRead = 99; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid permission read", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid permission write value", [this](const FDoneDelegate& Done) + { + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("test_collection"); + Obj.Key = FString::Printf(TEXT("test_key_%s"), *GenerateShortId()); + Obj.Value = TEXT("{\"data\":\"test\"}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 99; + Objects.Add(Obj); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects).Next([this, Done](FNakamaStorageObjectAcksResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid permission write", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ReadStorage.Permissions", [this]() + { + LatentIt("should read public object from other user", [this](const FDoneDelegate& Done) + { + FString TestKey = FString::Printf(TEXT("public_key_%s"), *GenerateShortId()); + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("public_collection"); + Obj.Key = TestKey; + Obj.Value = TEXT("{\"data\":\"public\"}"); + Obj.PermissionRead = 2; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects) + .Next([this, TestKey](const FNakamaStorageObjectAcks&) + { + TArray ReadIds; + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("public_collection"); + ReadId.Key = TestKey; + ReadId.UserId = UserId; + ReadIds.Add(ReadId); + return Nakama::ReadStorageObjects(ClientConfig, Session2, ReadIds); + }) + .Next([this, Done](FNakamaStorageObjectsResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("User 2 can read public object", Result.Value.Objects.Num() > 0); + Done.Execute(); + }); + }); + + LatentIt("should not read private object from other user", [this](const FDoneDelegate& Done) + { + FString TestKey = FString::Printf(TEXT("private_key_%s"), *GenerateShortId()); + TArray Objects; + FNakamaWriteStorageObject Obj; + Obj.Collection = TEXT("private_collection"); + Obj.Key = TestKey; + Obj.Value = TEXT("{\"data\":\"private\"}"); + Obj.PermissionRead = 1; + Obj.PermissionWrite = 1; + Objects.Add(Obj); + Nakama::WriteStorageObjects(ClientConfig, Session, Objects) + .Next([this, TestKey](const FNakamaStorageObjectAcks&) + { + TArray ReadIds; + FNakamaReadStorageObjectId ReadId; + ReadId.Collection = TEXT("private_collection"); + ReadId.Key = TestKey; + ReadId.UserId = UserId; + ReadIds.Add(ReadId); + return Nakama::ReadStorageObjects(ClientConfig, Session2, ReadIds); + }) + .Next([this, Done](FNakamaStorageObjectsResult Result) + { + if (!Result.bIsError) { TestTrue("Private object not visible to other user", Result.Value.Objects.Num() == 0); } + else { TestTrue("Server protected private object", true); } + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// CHANNEL EXTENDED TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncChannelExtSpec, "IntegrationTests.Nakama.ChannelExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FString UserId; + FString GroupId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncChannelExtSpec) + + +void FNakamaAsyncChannelExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")) + .Next([this](const FNakamaSession& AuthResult) + { + Session = AuthResult; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + return Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("channelgroup_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100); + }) + .Next([this, Done](FNakamaGroupResult GroupResult) + { + ASYNC_FAIL_ON_ERROR(GroupResult, Done); + GroupId = GroupResult.Value.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("ListChannelMessages.Extended", [this]() + { + LatentIt("should reject invalid channel ID format", [this](const FDoneDelegate& Done) + { + FString ChannelId = FString::Printf(TEXT("2.%s"), *GroupId); + Nakama::ListChannelMessages(ClientConfig, Session, ChannelId, 100, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error for channel not joined via WebSocket")); Done.Execute(); return; } + TestTrue("Got expected invalid channel error", !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should list messages backward (newest first)", [this](const FDoneDelegate& Done) + { + FString ChannelId = FString::Printf(TEXT("2.backwardroom_%s"), *GenerateShortId()); + Nakama::ListChannelMessages(ClientConfig, Session, ChannelId, 100, false, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + + LatentIt("should fail with invalid channel format", [this](const FDoneDelegate& Done) + { + Nakama::ListChannelMessages(ClientConfig, Session, TEXT("invalid_channel_format"), 100, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for invalid channel format", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should fail with group channel for non-existent group", [this](const FDoneDelegate& Done) + { + Nakama::ListChannelMessages(ClientConfig, Session, TEXT("2.ffffffff-ffff-ffff-ffff-ffffffffffff"), 100, true, TEXT("")).Next([this, Done](FNakamaChannelMessageListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for non-existent group channel", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// LINKING EXTENDED TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncLinkExtSpec, "IntegrationTests.Nakama.LinkExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession Session2; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncLinkExtSpec) + + +void FNakamaAsyncLinkExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account1; + Account1.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account1.Id, true, TEXT("")) + .Next([this](const FNakamaSession& Result) + { + Session = Result; + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")); + }) + .Next([this, Done](FNakamaSessionResult Result2) + { + ASYNC_FAIL_ON_ERROR(Result2, Done); + Session2 = Result2.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + auto DeleteMain = [this, Done]() + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }; + + if (Session2.Token.IsEmpty()) { DeleteMain(); return; } + Nakama::DeleteAccount(ClientConfig, Session2).Next([DeleteMain](FNakamaVoidResult) { DeleteMain(); }); + }); + + + + Describe("LinkCustom.AlreadyLinked", [this]() + { + LatentIt("should fail when custom ID already linked to another account", [this](const FDoneDelegate& Done) + { + FString CustomId = GenerateId(); + Nakama::LinkCustom(ClientConfig, Session, CustomId, TMap()) + .Next([this, CustomId](const FNakamaVoid&) + { + return Nakama::LinkCustom(ClientConfig, Session2, CustomId, TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got ALREADY_EXISTS error", Result.Error.Code == ENakamaErrorCode::AlreadyExists || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("LinkDevice.AlreadyLinked", [this]() + { + LatentIt("should fail when device ID already linked to another account", [this](const FDoneDelegate& Done) + { + FString DeviceId = GenerateId(); + Nakama::LinkDevice(ClientConfig, Session, DeviceId, TMap()) + .Next([this, DeviceId](const FNakamaVoid&) + { + return Nakama::LinkDevice(ClientConfig, Session2, DeviceId, TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got ALREADY_EXISTS error", Result.Error.Code == ENakamaErrorCode::AlreadyExists || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("LinkEmail.AlreadyLinked", [this]() + { + LatentIt("should fail when email already linked to another account", [this](const FDoneDelegate& Done) + { + FString Email = FString::Printf(TEXT("linked_%s@example.com"), *GenerateShortId()); + Nakama::LinkEmail(ClientConfig, Session, Email, TEXT("password123"), TMap()) + .Next([this, Email](const FNakamaVoid&) + { + return Nakama::LinkEmail(ClientConfig, Session2, Email, TEXT("password123"), TMap()); + }) + .Next([this, Done](FNakamaVoidResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got ALREADY_EXISTS error", Result.Error.Code == ENakamaErrorCode::AlreadyExists || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Unlink.NotLinked", [this]() + { + LatentIt("should succeed when unlinking not-linked custom ID", [this](const FDoneDelegate& Done) + { + Nakama::UnlinkCustom(ClientConfig, Session, GenerateId(), TMap()).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + + LatentIt("should succeed when unlinking not-linked device ID", [this](const FDoneDelegate& Done) + { + Nakama::UnlinkDevice(ClientConfig, Session, GenerateId(), TMap()).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + + LatentIt("should succeed when unlinking not-linked email", [this](const FDoneDelegate& Done) + { + Nakama::UnlinkEmail(ClientConfig, Session, FString::Printf(TEXT("notlinked_%s@example.com"), *GenerateShortId()), TEXT("password123"), TMap()).Next([this, Done](FNakamaVoidResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// RPC EXTENDED TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncRpcExtSpec, "IntegrationTests.Nakama.RPCExt", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + static const FString HttpKey; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncRpcExtSpec) + +const FString FNakamaAsyncRpcExtSpec::HttpKey = TEXT("defaulthttpkey"); + +void FNakamaAsyncRpcExtSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("RpcFunc.WithPayload", [this]() + { + LatentIt("should return NOT_FOUND for non-existent RPC", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("test"), TEXT("data")); + Nakama::RpcFunc(ClientConfig, Session, TEXT("test_echo"), Payload, TEXT("")).Next([this, Done](FNakamaRpcResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected NOT_FOUND error but RPC succeeded")); Done.Execute(); return; } + TestTrue("Got NOT_FOUND for non-existent RPC", + Result.Error.Code == ENakamaErrorCode::NotFound || + Result.Error.Message.Contains(TEXT("not found"), ESearchCase::IgnoreCase)); + Done.Execute(); + }); + }); + + LatentIt("should return NOT_FOUND for non-existent RPC with empty payload", [this](const FDoneDelegate& Done) + { + Nakama::RpcFunc(ClientConfig, Session, TEXT("test_echo"), nullptr, TEXT("")).Next([this, Done](FNakamaRpcResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected NOT_FOUND error but RPC succeeded")); Done.Execute(); return; } + TestTrue("Got NOT_FOUND for non-existent RPC", + Result.Error.Code == ENakamaErrorCode::NotFound || + Result.Error.Message.Contains(TEXT("not found"), ESearchCase::IgnoreCase)); + Done.Execute(); + }); + }); + }); + + Describe("RpcFunc.WithHttpKey", [this]() + { + LatentIt("should execute RPC with HTTP key (server-to-server)", [this](const FDoneDelegate& Done) + { + TSharedPtr ServerPayload = MakeShared(); + ServerPayload->SetStringField(TEXT("server"), TEXT("call")); + Nakama::RpcFunc(ClientConfig, HttpKey, TEXT("test_echo"), ServerPayload).Next([this, Done](FNakamaRpcResult Result) + { + TestTrue("Got expected response", true); + Done.Execute(); + }); + }); + }); + + Describe("RpcFunc.Validation", [this]() + { + LatentIt("should fail with RPC that returns error", [this](const FDoneDelegate& Done) + { + Nakama::RpcFunc(ClientConfig, Session, TEXT("test_error"), nullptr, TEXT("")).Next([this, Done](FNakamaRpcResult Result) + { + if (Result.bIsError) { TestTrue("Got error as expected", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); } + else { TestTrue("Got response", true); } + Done.Execute(); + }); + }); + }); + + Describe("RpcFunc.Transform", [this]() + { + LatentIt("should transform a message", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("message"), TEXT("hello")); + Nakama::RpcFunc(ClientConfig, Session, TEXT("transform"), Payload, TEXT("")).Next([this, Done](FNakamaRpcResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TSharedPtr Response; + TSharedRef> Reader = TJsonReaderFactory<>::Create(Result.Value.Payload); + if (!FJsonSerializer::Deserialize(Reader, Response) || !Response.IsValid()) + { + AddError(TEXT("Failed to parse transform RPC response JSON")); + Done.Execute(); + return; + } + TestEqual("original matches", Response->GetStringField(TEXT("original")), FString(TEXT("hello"))); + TestEqual("reversed matches", Response->GetStringField(TEXT("reversed")), FString(TEXT("olleh"))); + TestEqual("upper matches", Response->GetStringField(TEXT("upper")), FString(TEXT("HELLO"))); + TestEqual("length matches", static_cast(Response->GetNumberField(TEXT("length"))), 5); + Done.Execute(); + }); + }); + + LatentIt("should return error for missing message field", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("foo"), TEXT("bar")); + Nakama::RpcFunc(ClientConfig, Session, TEXT("transform"), Payload, TEXT("")).Next([this, Done](FNakamaRpcResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error for missing message field but RPC succeeded")); Done.Execute(); return; } + TestTrue("Got error for missing message field", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should transform via HTTP key", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("message"), TEXT("world")); + Nakama::RpcFunc(ClientConfig, HttpKey, TEXT("transform"), Payload).Next([this, Done](FNakamaRpcResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TSharedPtr Response; + TSharedRef> Reader = TJsonReaderFactory<>::Create(Result.Value.Payload); + if (!FJsonSerializer::Deserialize(Reader, Response) || !Response.IsValid()) + { + AddError(TEXT("Failed to parse transform RPC response JSON")); + Done.Execute(); + return; + } + TestEqual("original matches", Response->GetStringField(TEXT("original")), FString(TEXT("world"))); + TestEqual("reversed matches", Response->GetStringField(TEXT("reversed")), FString(TEXT("dlrow"))); + TestEqual("upper matches", Response->GetStringField(TEXT("upper")), FString(TEXT("WORLD"))); + TestEqual("length matches", static_cast(Response->GetNumberField(TEXT("length"))), 5); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCHES VALIDATION TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncMatchesValidationSpec, "IntegrationTests.Nakama.MatchesValidation", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncMatchesValidationSpec) + + +void FNakamaAsyncMatchesValidationSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + Describe("ListMatches.Validation", [this]() + { + LatentIt("should fail with limit too high", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 2000, false, TEXT(""), 0, 100, TEXT("")).Next([this, Done](FNakamaMatchListResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for limit too high", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should list with combined filters", [this](const FDoneDelegate& Done) + { + Nakama::ListMatches(ClientConfig, Session, 10, true, TEXT(""), 2, 8, TEXT("")).Next([this, Done](FNakamaMatchListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + for (const auto& Match : Result.Value.Matches) + { + TestTrue("Match is authoritative", Match.Authoritative); + TestTrue("Match size >= min", Match.Size >= 2); + TestTrue("Match size <= max", Match.Size <= 8); + } + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// PURCHASES/SUBSCRIPTIONS TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncPurchasesSpec, "IntegrationTests.Nakama.Purchases", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncPurchasesSpec) + + +void FNakamaAsyncPurchasesSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + Describe("ListSubscriptions", [this]() + { + LatentIt("should list subscriptions (empty if none)", [this](const FDoneDelegate& Done) + { + Nakama::ListSubscriptions(ClientConfig, Session, 100, TEXT("")).Next([this, Done](FNakamaSubscriptionListResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("ListSubscriptions succeeded", true); + Done.Execute(); + }); + }); + }); + + Describe("ValidatePurchase.Apple", [this]() + { + LatentIt("should fail with empty receipt", [this](const FDoneDelegate& Done) + { + Nakama::ValidatePurchaseApple(ClientConfig, Session, TEXT(""), false).Next([this, Done](FNakamaValidatePurchaseResponseResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty receipt", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ValidatePurchase.Google", [this]() + { + LatentIt("should fail with empty purchase", [this](const FDoneDelegate& Done) + { + Nakama::ValidatePurchaseGoogle(ClientConfig, Session, TEXT(""), false).Next([this, Done](FNakamaValidatePurchaseResponseResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty purchase", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ValidateSubscription.Apple", [this]() + { + LatentIt("should fail with empty receipt", [this](const FDoneDelegate& Done) + { + Nakama::ValidateSubscriptionApple(ClientConfig, Session, TEXT(""), false).Next([this, Done](FNakamaValidateSubscriptionResponseResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty receipt", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("ValidateSubscription.Google", [this]() + { + LatentIt("should fail with empty receipt", [this](const FDoneDelegate& Done) + { + Nakama::ValidateSubscriptionGoogle(ClientConfig, Session, TEXT(""), false).Next([this, Done](FNakamaValidateSubscriptionResponseResult Result) + { + if (!Result.bIsError) { AddError(TEXT("Expected error but got success")); Done.Execute(); return; } + TestTrue("Got error for empty receipt", Result.Error.Code != 0 || !Result.Error.Message.IsEmpty()); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// EVENT TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncEventSpec, "IntegrationTests.Nakama.Event", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncEventSpec) + + +void FNakamaAsyncEventSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + Describe("Event", [this]() + { + LatentIt("should submit event successfully", [this](const FDoneDelegate& Done) + { + TMap Properties; + Properties.Add(TEXT("source"), TEXT("test")); + Nakama::Event(ClientConfig, Session, TEXT("test_event"), FDateTime::UtcNow().ToIso8601(), false, Properties).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Event submitted successfully", true); + Done.Execute(); + }); + }); + + LatentIt("should accept empty event name", [this](const FDoneDelegate& Done) + { + TMap Properties; + Nakama::Event(ClientConfig, Session, TEXT(""), FDateTime::UtcNow().ToIso8601(), false, Properties).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Event with empty name accepted", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// HEALTHCHECK TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncHealthcheckSpec, "IntegrationTests.Nakama.Healthcheck", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + static const FString HttpKey; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncHealthcheckSpec) + +const FString FNakamaAsyncHealthcheckSpec::HttpKey = TEXT("defaulthttpkey"); + +void FNakamaAsyncHealthcheckSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + Describe("Healthcheck", [this]() + { + LatentIt("should return healthy status with session", [this](const FDoneDelegate& Done) + { + Nakama::Healthcheck(ClientConfig, Session).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Server is healthy", true); + Done.Execute(); + }); + }); + + LatentIt("should return healthy status with HTTP key", [this](const FDoneDelegate& Done) + { + Nakama::Healthcheck(ClientConfig, HttpKey).Next([this, Done](FNakamaVoidResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Server is healthy with HTTP key", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// GROUP USER MANAGEMENT TESTS (Async) +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncGroupUsersSpec, "IntegrationTests.Nakama.GroupUsers", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + FNakamaSession Session2; + FNakamaSession Session3; + FString UserId; + FString UserId2; + FString UserId3; + FString GroupId; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateShortId() { return FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + +END_DEFINE_SPEC(FNakamaAsyncGroupUsersSpec) + + +void FNakamaAsyncGroupUsersSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account1; + Account1.Id = GenerateId(); + Nakama::AuthenticateCustom(ClientConfig, Account1.Id, true, TEXT("")) + .Next([this](const FNakamaSession& Result) + { + Session = Result; + return Nakama::GetAccount(ClientConfig, Session); + }) + .Next([this](const FNakamaAccount& AccResult) + { + UserId = AccResult.User.Id; + FNakamaAccountCustom Account2; + Account2.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account2.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& Result2) + { + Session2 = Result2; + return Nakama::GetAccount(ClientConfig, Session2); + }) + .Next([this](const FNakamaAccount& AccResult2) + { + UserId2 = AccResult2.User.Id; + FNakamaAccountCustom Account3; + Account3.Id = GenerateId(); + return Nakama::AuthenticateCustom(ClientConfig, Account3.Id, true, TEXT("")); + }) + .Next([this](const FNakamaSession& Result3) + { + Session3 = Result3; + return Nakama::GetAccount(ClientConfig, Session3); + }) + .Next([this, Done](FNakamaAccountResult AccResult3) + { + ASYNC_FAIL_ON_ERROR(AccResult3, Done); + UserId3 = AccResult3.Value.User.Id; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + Nakama::DeleteAccount(ClientConfig, Session3) + .Next([this](FNakamaVoidResult) { return Nakama::DeleteAccount(ClientConfig, Session2); }) + .Next([this](FNakamaVoidResult) { return Nakama::DeleteAccount(ClientConfig, Session); }) + .Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + + + Describe("AddGroupUsers", [this]() + { + LatentIt("should add user to group by owner", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("addusers_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::AddGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult AddResult) + { + ASYNC_FAIL_ON_ERROR(AddResult, Done); + TestTrue("User added to group", true); + Done.Execute(); + }); + }); + }); + + Describe("KickGroupUsers", [this]() + { + LatentIt("should kick user from group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("kickusers_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + return Nakama::JoinGroup(ClientConfig, Session2, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::KickGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult KickResult) + { + ASYNC_FAIL_ON_ERROR(KickResult, Done); + TestTrue("User kicked successfully", true); + Done.Execute(); + }); + }); + }); + + Describe("BanGroupUsers", [this]() + { + LatentIt("should ban user from group", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("banusers_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + return Nakama::JoinGroup(ClientConfig, Session2, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::BanGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult BanResult) + { + ASYNC_FAIL_ON_ERROR(BanResult, Done); + TestTrue("User banned successfully", true); + Done.Execute(); + }); + }); + + LatentIt("should prevent banned user from rejoining", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("banrejoin_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + return Nakama::JoinGroup(ClientConfig, Session2, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::BanGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult BanResult) + { + ASYNC_FAIL_ON_ERROR(BanResult, Done); + Nakama::JoinGroup(ClientConfig, Session2, GroupId).Next([this, Done](FNakamaVoidResult RejoinResult) + { + if (RejoinResult.bIsError) + { + TestTrue("Banned user cannot rejoin", true); + Done.Execute(); + return; + } + Nakama::ListGroupUsers(ClientConfig, Session, GroupId, 100, 0, TEXT("")).Next([this, Done](FNakamaGroupUserListResult ListResult) + { + ASYNC_FAIL_ON_ERROR(ListResult, Done); + bool bFoundUser = false; + for (const auto& GU : ListResult.Value.GroupUsers) + { + if (GU.User.Id == UserId2) { bFoundUser = true; break; } + } + TestFalse("Banned user should not be in group membership", bFoundUser); + Done.Execute(); + }); + }); + }); + }); + }); + + Describe("PromoteGroupUsers", [this]() + { + LatentIt("should promote user to admin", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("promote_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + return Nakama::JoinGroup(ClientConfig, Session2, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::PromoteGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult PromoteResult) + { + ASYNC_FAIL_ON_ERROR(PromoteResult, Done); + TestTrue("User promoted successfully", true); + Done.Execute(); + }); + }); + }); + + Describe("DemoteGroupUsers", [this]() + { + LatentIt("should demote admin to member", [this](const FDoneDelegate& Done) + { + Nakama::CreateGroup(ClientConfig, Session, FString::Printf(TEXT("demote_%s"), *GenerateShortId()), TEXT(""), TEXT(""), TEXT(""), true, 100) + .Next([this](const FNakamaGroup& Result) + { + GroupId = Result.Id; + return Nakama::JoinGroup(ClientConfig, Session2, GroupId); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::PromoteGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this](const FNakamaVoid&) + { + TArray UserIds; + UserIds.Add(UserId2); + return Nakama::DemoteGroupUsers(ClientConfig, Session, GroupId, UserIds); + }) + .Next([this, Done](FNakamaVoidResult DemoteResult) + { + ASYNC_FAIL_ON_ERROR(DemoteResult, Done); + TestTrue("User demoted successfully", true); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// CANCELLATION TOKEN TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncCancellationSpec, "IntegrationTests.Nakama.Cancellation", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncCancellationSpec) + + +void FNakamaAsyncCancellationSpec::Define() +{ + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT("")).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("CancellationToken", [this]() + { + LatentIt("should return error immediately with pre-cancelled token on GetAccount", [this](const FDoneDelegate& Done) + { + TSharedRef> Token = MakeShared>(true); + Nakama::GetAccount(ClientConfig, Session, {}, Token).Next([this, Done](FNakamaAccountResult Result) + { + TestTrue("Request is error", Result.bIsError); + TestEqual("Error code is -1 (cancelled)", Result.Error.Code, -1); + Done.Execute(); + }); + }); + + LatentIt("should succeed with uncancelled token", [this](const FDoneDelegate& Done) + { + TSharedRef> Token = MakeShared>(false); + Nakama::GetAccount(ClientConfig, Session, {}, Token).Next([this, Done](FNakamaAccountResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + TestTrue("Got valid user ID", !Result.Value.User.Id.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should return error with pre-cancelled token on RpcFunc", [this](const FDoneDelegate& Done) + { + TSharedRef> Token = MakeShared>(true); + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("message"), TEXT("hello")); + Nakama::RpcFunc(ClientConfig, Session, TEXT("transform"), Payload, TEXT(""), {}, Token).Next([this, Done](FNakamaRpcResult Result) + { + TestTrue("Request is error", Result.bIsError); + TestEqual("Error code is -1 (cancelled)", Result.Error.Code, -1); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// RETRY TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaAsyncRetrySpec, "IntegrationTests.Nakama.Retry", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaRetryConfig RetryConfig; + FNakamaSession Session; + + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaAsyncRetrySpec) + + +void FNakamaAsyncRetrySpec::Define() +{ + BeforeEach([this]() + { + RetryConfig.MaxRetries = 2; + RetryConfig.BaseDelayMs = 100; + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + FNakamaAccountCustom Account; + Account.Id = GenerateId(); + + Nakama::AuthenticateCustom(ClientConfig, Account.Id, true, TEXT(""), {}, RetryConfig).Next([this, Done](FNakamaSessionResult AuthResult) + { + if (AuthResult.bIsError) { AddError(FString::Printf(TEXT("Auth failed: %s"), *AuthResult.Error.Message)); Done.Execute(); return; } + Session = AuthResult.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Retry", [this]() + { + LatentIt("should exhaust retries on persistent transient error", [this](const FDoneDelegate& Done) + { + Nakama::RpcFunc(ClientConfig, Session, TEXT("always_fail"), nullptr, TEXT(""), RetryConfig).Next([this, Done](FNakamaRpcResult Result) + { + TestTrue("Request failed", Result.bIsError); + TestEqual("Error code is 14 (UNAVAILABLE)", Result.Error.Code, 14); + Done.Execute(); + }); + }); + + LatentIt("should succeed after transient failures within retry budget", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("test_id"), GenerateId()); + Payload->SetNumberField(TEXT("fail_count"), 2); + + Nakama::RpcFunc(ClientConfig, Session, TEXT("retry_test"), Payload, TEXT(""), RetryConfig).Next([this, Done](FNakamaRpcResult Result) + { + ASYNC_FAIL_ON_ERROR(Result, Done); + + TSharedPtr Response; + TSharedRef> Reader = TJsonReaderFactory<>::Create(Result.Value.Payload); + if (!FJsonSerializer::Deserialize(Reader, Response) || !Response.IsValid()) + { + AddError(TEXT("Failed to parse RPC response JSON")); + Done.Execute(); + return; + } + + TestEqual("Took 3 attempts (1 original + 2 retries)", static_cast(Response->GetNumberField(TEXT("attempts"))), 3); + Done.Execute(); + }); + }); + + LatentIt("should fail immediately on non-transient error without retry", [this](const FDoneDelegate& Done) + { + TSharedPtr Payload = MakeShared(); + Payload->SetStringField(TEXT("foo"), TEXT("bar")); // missing "message" field triggers INVALID_ARGUMENT + + Nakama::RpcFunc(ClientConfig, Session, TEXT("transform"), Payload, TEXT(""), RetryConfig).Next([this, Done](FNakamaRpcResult Result) + { + TestTrue("Request failed", Result.bIsError); + TestEqual("Error code is 3 (INVALID_ARGUMENT)", Result.Error.Code, 3); + Done.Execute(); + }); + }); + + LatentIt("should not retry when MaxRetries is zero", [this](const FDoneDelegate& Done) + { + RetryConfig.MaxRetries = 0; + + Nakama::RpcFunc(ClientConfig, Session, TEXT("always_fail"), nullptr, TEXT(""), RetryConfig).Next([this, Done](FNakamaRpcResult Result) + { + TestTrue("Request failed", Result.bIsError); + TestEqual("Error code is 14 (UNAVAILABLE)", Result.Error.Code, 14); + Done.Execute(); + }); + }); + + LatentIt("should retry on connection failure (unreachable host)", [this](const FDoneDelegate& Done) + { + FNakamaClientConfig BadConfig{TEXT("defaultkey"), TEXT("127.0.0.1"), 19999, false}; + FNakamaRetryConfig ConnRetryConfig; + ConnRetryConfig.MaxRetries = 1; + ConnRetryConfig.BaseDelayMs = 100; + ConnRetryConfig.Timeout = 2.0f; + + double StartTime = FPlatformTime::Seconds(); + Nakama::GetAccount(BadConfig, Session, ConnRetryConfig).Next([this, Done, StartTime](FNakamaAccountResult Result) + { + TestTrue("Request failed", Result.bIsError); + TestEqual("Error code is 0 (connection failed)", Result.Error.Code, 0); + double Elapsed = FPlatformTime::Seconds() - StartTime; + TestTrue("Took long enough to have retried (>0.1s)", Elapsed > 0.1); + Done.Execute(); + }); + }); + + LatentIt("should report expired refresh token as UNAUTHENTICATED (code 16)", [this](const FDoneDelegate& Done) + { + // Force both tokens to appear expired so MaybeRefreshThenCall + // hits the IsRefreshExpired() path. + Session.TokenExpiresAt = 1; // auth token expired (Unix epoch + 1s) + Session.RefreshTokenExpiresAt = 1; // refresh token also expired + + FNakamaRetryConfig RefreshRetryConfig; + RefreshRetryConfig.MaxRetries = 0; + RefreshRetryConfig.bAutoRefreshSession = true; + + Nakama::GetAccount(ClientConfig, Session, RefreshRetryConfig).Next([this, Done](FNakamaAccountResult Result) + { + TestTrue("Request failed", Result.bIsError); + TestEqual("Error code is UNAUTHENTICATED (16)", Result.Error.Code, 16); + Done.Execute(); + }); + }); + + LatentIt("should safely skip OnSessionRefreshed when owner UObject is GC'd", [this](const FDoneDelegate& Done) + { + // Create a temporary UObject that we will destroy before the callback fires. + // This simulates a common pattern: a UObject (e.g. a GameInstance subsystem + // or controller) captures `this` in OnSessionRefreshed, then gets GC'd while + // a retry/refresh is in-flight. + UObject* Victim = NewObject(nullptr, TEXT("/Temp/NakamaTestVictim"), RF_Transient); + Victim->AddToRoot(); // prevent premature GC so we control the lifetime + + // Capture the raw pointer but also set OnSessionRefreshedOwner so the + // retry logic can detect when the owner has been collected. + TSharedRef CallbackFired = MakeShared(false); + FNakamaRetryConfig RefreshRetryConfig; + RefreshRetryConfig.MaxRetries = 0; + RefreshRetryConfig.bAutoRefreshSession = true; + RefreshRetryConfig.OnSessionRefreshedOwner = Victim; + RefreshRetryConfig.OnSessionRefreshed = [Victim, CallbackFired](const FNakamaSession& RefreshedSession) + { + *CallbackFired = true; + // If we get here with a GC'd Victim, this would crash. + // The weak owner guard should prevent this from ever executing. + UE_LOG(LogTemp, Log, TEXT("OnSessionRefreshed called, Victim class: %s"), + *Victim->GetClass()->GetName()); + }; + + // Force the auth token to appear expired so the retry logic triggers + // a session refresh (but keep the refresh token valid). + Session.TokenExpiresAt = 1; + + // Remove the root reference and force GC so the weak pointer becomes stale. + // TWeakObjectPtr only invalidates after the GC clears the weak reference table. + Victim->RemoveFromRoot(); + Victim->MarkAsGarbage(); + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // The weak pointer is now stale. The API call will trigger + // MaybeRefreshThenCall -> SessionRefresh, but OnSessionRefreshed + // should be skipped because OnSessionRefreshedOwner is no longer valid. + Nakama::GetAccount(ClientConfig, Session, RefreshRetryConfig).Next([this, Done, CallbackFired](FNakamaAccountResult Result) + { + TestFalse("OnSessionRefreshed should NOT have fired after owner was GC'd", *CallbackFired); + Done.Execute(); + }); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/NakamaWebSocketSubsystemTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/NakamaWebSocketSubsystemTests.cpp new file mode 100644 index 000000000..521effbb8 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/NakamaWebSocketSubsystemTests.cpp @@ -0,0 +1,1150 @@ +/** + * Nakama Realtime (WebSocket) Integration Test Suite + * + * Tests UNakamaWebSocketSubsystem connection lifecycle, request/response round-trips, + * delegate notifications, reconnection, error paths, and concurrent requests. + * + * Requires a running Nakama server (IntegrationTests/server/docker-compose.yml). + * + * Run with: + * -ExecCmds="Automation RunTests IntegrationTests.NakamaWebSocketSubsystem" + */ + +#include +#include "Misc/AutomationTest.h" +#include "Misc/Guid.h" +#include "Dom/JsonObject.h" +#include "Nakama.h" +#include "NakamaWebSocketSubsystem.h" +#include "WebSocketsModule.h" +#include "Engine/Engine.h" +#include "Engine/GameInstance.h" + +// --------------------------------------------------------------------------- +// Shared constants (file-local) +// --------------------------------------------------------------------------- + +namespace +{ + static const FString RtServerKey = TEXT("defaultkey"); + static const FString RtHost = TEXT("127.0.0.1"); + static constexpr int32 RtPort = 7350; + + /** Build connection params with a long ping interval so auto-pings don't + * pollute test assertions. */ + FNakamaWebSocketConnectionParams MakeConnParams(const FNakamaSession& Session, + int32 OverridePort = RtPort) + { + FNakamaWebSocketConnectionParams P; + P.Host = RtHost; + P.Port = OverridePort; + P.Token = Session.Token; + P.PingIntervalSeconds = 60.0f; + P.bUseSSL = false; + return P; + } + + /** Create a standalone UNakamaWebSocketSubsystem for testing. + * + * UNakamaWebSocketSubsystem::Initialize() only loads the WebSockets module; + * none of its actual Connect/Send/Close logic depends on a UGameInstance + * outer. We therefore create the object directly and ensure the module is + * loaded, skipping the subsystem-collection machinery entirely. + * + * The caller must AddToRoot() the returned object and RemoveFromRoot() + * after use. */ + UNakamaWebSocketSubsystem* CreateTestSubsystem() + { + if (!FModuleManager::Get().IsModuleLoaded(TEXT("WebSockets"))) + { + FModuleManager::Get().LoadModule(TEXT("WebSockets")); + } + // UNakamaWebSocketSubsystem has ClassWithin=UGameInstance (UGameInstanceSubsystem), + // so it must have a UGameInstance outer to satisfy UE's ensure. + UGameInstance* TempInstance = NewObject(GetTransientPackage()); + return NewObject(TempInstance); + } +} + +/** + * Fails the current test and calls Done when a FNakamaWebSocketResponse carries an + * error. Use inside a .Next() callback that expects success. + */ +#define WSS_FAIL_ON_ERROR(Resp, Done) \ + if ((Resp).ErrorCode != ENakamaWebSocketError::None) { \ + AddError(TEXT("Unexpected realtime error in response")); \ + (Done).Execute(); \ + return; \ + } + +// ============================================================================ +// CONNECTION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemConnectionSpec, "IntegrationTests.NakamaWebSocketSubsystem.Connection", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemConnectionSpec) + +void FNakamaWebSocketSubsystemConnectionSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult Result) + { + if (Result.bIsError) + { + AddError(FString::Printf(TEXT("Auth failed: %s"), *Result.Error.Message)); + Done.Execute(); + return; + } + Session = Result.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + // ----------------------------------------------------------------------- + // Success path + // ----------------------------------------------------------------------- + + Describe("Success", [this]() + { + LatentIt("should connect with a valid session token", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)).Next([this, Done](FNakamaWebSocketConnectionResult Result) + { + TestFalse("Connection should succeed (bError = false)", Result.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); + + // ----------------------------------------------------------------------- + // Invalid token + // ----------------------------------------------------------------------- + + Describe("InvalidToken", [this]() + { + LatentIt("should fail with a malformed token", [this](const FDoneDelegate& Done) + { + FNakamaWebSocketConnectionParams Bad = MakeConnParams(Session); + Bad.Token = TEXT("not.a.valid.jwt"); + + WSSub->Connect(Bad).Next([this, Done](FNakamaWebSocketConnectionResult Result) + { + TestTrue("Connection with invalid token should report error", Result.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + + LatentIt("should fail with an empty token", [this](const FDoneDelegate& Done) + { + FNakamaWebSocketConnectionParams Bad = MakeConnParams(Session); + Bad.Token = TEXT(""); + + WSSub->Connect(Bad).Next([this, Done](FNakamaWebSocketConnectionResult Result) + { + TestTrue("Connection with empty token should report error", Result.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); + + // ----------------------------------------------------------------------- + // Wrong endpoint + // ----------------------------------------------------------------------- + + Describe("WrongEndpoint", [this]() + { + LatentIt("should fail when no server is listening on the target port", [this](const FDoneDelegate& Done) + { + // Port 19999 — nothing should be listening here. + FNakamaWebSocketConnectionParams Bad = MakeConnParams(Session, 19999); + + WSSub->Connect(Bad).Next([this, Done](FNakamaWebSocketConnectionResult Result) + { + TestTrue("Connection to unreachable port should report error", Result.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); + + // ----------------------------------------------------------------------- + // Reconnection + // ----------------------------------------------------------------------- + + Describe("Reconnect", [this]() + { + LatentIt("should reconnect successfully after an explicit Close", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult First) -> TNakamaFuture + { + TestFalse("First connection should succeed", First.ErrorCode != ENakamaWebSocketError::None); + WSSub->Close(); + return WSSub->Connect(MakeConnParams(Session)); + }) + .Next([this, Done](FNakamaWebSocketConnectionResult Second) + { + TestFalse("Connection after explicit Close should succeed", Second.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + + LatentIt("should accept a new Connect while already connected", [this](const FDoneDelegate& Done) + { + // A second Connect while already connected — the subsystem should + // auto-close the old socket and open a new one. + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult First) -> TNakamaFuture + { + TestFalse("First connection should succeed", First.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Connect(MakeConnParams(Session)); + }) + .Next([this, Done](FNakamaWebSocketConnectionResult Second) + { + TestFalse("Second Connect while already connected should succeed", Second.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + + LatentIt("should be usable after reconnect (ping round-trip)", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult First) -> TNakamaFuture + { + TestFalse("First connection", First.ErrorCode != ENakamaWebSocketError::None); + WSSub->Close(); + return WSSub->Connect(MakeConnParams(Session)); + }) + .Next([this](FNakamaWebSocketConnectionResult Second) -> TNakamaFuture + { + TestFalse("Reconnection", Second.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("ping"), MakeShared()); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + TestFalse("Ping after reconnect should succeed", Resp.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// PING TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemPingSpec, "IntegrationTests.NakamaWebSocketSubsystem.Ping", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemPingSpec) + +void FNakamaWebSocketSubsystemPingSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Single", [this]() + { + LatentIt("should receive a pong response to a single ping", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("ping"), MakeShared()); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + TestFalse("Ping should receive a non-error pong response", Resp.ErrorCode != ENakamaWebSocketError::None); + Done.Execute(); + }); + }); + }); + + Describe("AutoPing", [this]() + { + LatentIt("should not accumulate pending requests (memory leak regression)", [this](const FDoneDelegate& Done) + { + // Use a fast ping interval so ~10 pings fire within 1 second. + FNakamaWebSocketConnectionParams P = MakeConnParams(Session); + P.PingIntervalSeconds = 0.1f; + + WSSub->Connect(P).Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + if (CR.ErrorCode != ENakamaWebSocketError::None) { Done.Execute(); return; } + + // After 1 s, ~10 auto-pings will have fired. Requests must still be empty. + TWeakObjectPtr WeakSub = WSSub; + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([this, Done, WeakSub](float) -> bool + { + if (UNakamaWebSocketSubsystem* Sub = WeakSub.Get()) + { + TestEqual(TEXT("Auto-ping must not leak entries into Requests"), Sub->GetPendingRequestCount(), 0); + } + Done.Execute(); + return false; + }), + 1.0f + ); + }); + }); + }); + + Describe("Concurrent", [this]() + { + LatentIt("should handle multiple concurrent pings and resolve all of them", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + + // Fire several pings simultaneously; track completions with a shared counter. + constexpr int32 NumPings = 5; + TSharedRef> Remaining = MakeShared>(NumPings); + + for (int32 i = 0; i < NumPings; ++i) + { + WSSub->Send(TEXT("ping"), MakeShared()) + .Next([this, Done, Remaining](FNakamaWebSocketResponse Resp) + { + if (Resp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("A concurrent ping returned an error")); + } + if (--(*Remaining) == 0) + { + Done.Execute(); + } + }); + } + }); + }); + + LatentIt("should handle pings interleaved with other requests", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + + // Send a match_create and two pings simultaneously; all three + // must resolve without error. + TSharedRef> Remaining = MakeShared>(3); + + auto Decrement = [this, Done, Remaining](FNakamaWebSocketResponse Resp) + { + if (Resp.ErrorCode != ENakamaWebSocketError::None) AddError(TEXT("Interleaved request returned an error")); + if (--(*Remaining) == 0) Done.Execute(); + }; + + WSSub->Send(TEXT("match_create"), MakeShared()).Next(Decrement); + WSSub->Send(TEXT("ping"), MakeShared()).Next(Decrement); + WSSub->Send(TEXT("ping"), MakeShared()).Next(Decrement); + }); + }); + }); +} + +// ============================================================================ +// CHANNEL TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemChannelSpec, "IntegrationTests.NakamaWebSocketSubsystem.Channel", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + FString GenerateRoomName(){ return TEXT("test-room-") + FGuid::NewGuid().ToString(EGuidFormats::Short).Left(8); } + + TSharedPtr MakeChannelJoinJson(const FString& RoomName) const + { + TSharedPtr J = MakeShared(); + J->SetStringField(TEXT("target"), RoomName); + J->SetNumberField(TEXT("type"), 1); // 1 = room channel + J->SetBoolField (TEXT("persistence"), false); + J->SetBoolField (TEXT("hidden"), false); + return J; + } + + FString ExtractChannelId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* ChannelObj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("channel"), ChannelObj)) + { + (*ChannelObj)->TryGetStringField(TEXT("id"), Id); + } + return Id; + } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemChannelSpec) + +void FNakamaWebSocketSubsystemChannelSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Join", [this]() + { + LatentIt("should join a room channel and receive a channel response", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("channel_join"), MakeChannelJoinJson(GenerateRoomName())); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + FString ChannelId = ExtractChannelId(Resp); + TestFalse("Joined channel should have a non-empty ID", ChannelId.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("SendMessage", [this]() + { + LatentIt("should send a message to a room channel and receive an ack", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("channel_join"), MakeChannelJoinJson(GenerateRoomName())); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("channel_join failed")); + return MakeCompletedFuture(JoinResp); + } + FString ChannelId = ExtractChannelId(JoinResp); + TestFalse("Channel ID from join", ChannelId.IsEmpty()); + + TSharedPtr SendJson = MakeShared(); + SendJson->SetStringField(TEXT("channel_id"), ChannelId); + SendJson->SetStringField(TEXT("content"), TEXT("{\"msg\":\"hello\"}")); + return WSSub->Send(TEXT("channel_message_send"), SendJson); + }) + .Next([this, Done](FNakamaWebSocketResponse SendResp) + { + WSS_FAIL_ON_ERROR(SendResp, Done); + TestTrue("Send ack response has data", SendResp.Data.IsValid()); + Done.Execute(); + }); + }); + }); + + Describe("Leave", [this]() + { + LatentIt("should leave a channel successfully", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("channel_join"), MakeChannelJoinJson(GenerateRoomName())); + }) + .Next([this](FNakamaWebSocketResponse JoinResp) -> TNakamaFuture + { + if (JoinResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("channel_join failed")); + return MakeCompletedFuture(JoinResp); + } + FString ChannelId = ExtractChannelId(JoinResp); + TestFalse("Channel ID from join", ChannelId.IsEmpty()); + + TSharedPtr LeaveJson = MakeShared(); + LeaveJson->SetStringField(TEXT("channel_id"), ChannelId); + return WSSub->Send(TEXT("channel_leave"), LeaveJson); + }) + .Next([this, Done](FNakamaWebSocketResponse LeaveResp) + { + WSS_FAIL_ON_ERROR(LeaveResp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCH TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemMatchSpec, "IntegrationTests.NakamaWebSocketSubsystem.Match", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + FString ExtractMatchId(const FNakamaWebSocketResponse& Resp) const + { + FString Id; + const TSharedPtr* MatchObj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("match"), MatchObj)) + { + (*MatchObj)->TryGetStringField(TEXT("match_id"), Id); + } + return Id; + } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemMatchSpec) + +void FNakamaWebSocketSubsystemMatchSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Create", [this]() + { + LatentIt("should create a relayed match and receive a match_id", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("match_create"), MakeShared()); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + FString MatchId = ExtractMatchId(Resp); + TestFalse("Created match should have a non-empty match_id", MatchId.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Leave", [this]() + { + LatentIt("should leave a match without error", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("match_create"), MakeShared()); + }) + .Next([this](FNakamaWebSocketResponse CreateResp) -> TNakamaFuture + { + if (CreateResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("match_create failed")); + return MakeCompletedFuture(CreateResp); + } + FString MatchId = ExtractMatchId(CreateResp); + TestFalse("Match ID from create", MatchId.IsEmpty()); + + TSharedPtr LeaveJson = MakeShared(); + LeaveJson->SetStringField(TEXT("match_id"), MatchId); + return WSSub->Send(TEXT("match_leave"), LeaveJson); + }) + .Next([this, Done](FNakamaWebSocketResponse LeaveResp) + { + WSS_FAIL_ON_ERROR(LeaveResp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MATCHMAKER TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemMatchmakerSpec, "IntegrationTests.NakamaWebSocketSubsystem.Matchmaker", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + + TSharedPtr MakeMatchmakerAddJson() const + { + TSharedPtr J = MakeShared(); + J->SetNumberField(TEXT("min_count"), 2); + J->SetNumberField(TEXT("max_count"), 4); + J->SetStringField(TEXT("query"), TEXT("*")); + return J; + } + + FString ExtractTicket(const FNakamaWebSocketResponse& Resp) const + { + FString Ticket; + const TSharedPtr* TicketObj; + if (Resp.Data.IsValid() && Resp.Data->TryGetObjectField(TEXT("matchmaker_ticket"), TicketObj)) + { + (*TicketObj)->TryGetStringField(TEXT("ticket"), Ticket); + } + return Ticket; + } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemMatchmakerSpec) + +void FNakamaWebSocketSubsystemMatchmakerSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Add", [this]() + { + LatentIt("should add to matchmaker and receive a ticket", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("matchmaker_add"), MakeMatchmakerAddJson()); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + FString Ticket = ExtractTicket(Resp); + TestFalse("Matchmaker ticket should not be empty", Ticket.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("Remove", [this]() + { + LatentIt("should add to matchmaker and then cancel with the ticket", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("matchmaker_add"), MakeMatchmakerAddJson()); + }) + .Next([this](FNakamaWebSocketResponse AddResp) -> TNakamaFuture + { + if (AddResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("matchmaker_add failed")); + return MakeCompletedFuture(AddResp); + } + FString Ticket = ExtractTicket(AddResp); + TestFalse("Ticket before remove", Ticket.IsEmpty()); + + TSharedPtr RemoveJson = MakeShared(); + RemoveJson->SetStringField(TEXT("ticket"), Ticket); + return WSSub->Send(TEXT("matchmaker_remove"), RemoveJson); + }) + .Next([this, Done](FNakamaWebSocketResponse RemoveResp) + { + WSS_FAIL_ON_ERROR(RemoveResp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// STATUS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemStatusSpec, "IntegrationTests.NakamaWebSocketSubsystem.Status", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemStatusSpec) + +void FNakamaWebSocketSubsystemStatusSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("Update", [this]() + { + LatentIt("should set a user status string", [this](const FDoneDelegate& Done) + { + TSharedPtr Json = MakeShared(); + Json->SetStringField(TEXT("status"), TEXT("Playing a game")); + + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Json](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("status_update"), Json); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + + LatentIt("should clear user status by omitting the status field", [this](const FDoneDelegate& Done) + { + // Sending status_update with no "status" field signals going offline. + WSSub->Connect(MakeConnParams(Session)) + .Next([this](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("status_update"), MakeShared()); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + }); + + Describe("Follow", [this]() + { + LatentIt("should follow a user ID without error", [this](const FDoneDelegate& Done) + { + // Use a random UUID — Nakama accepts follow requests for + // non-existent users and returns an empty presence list. + TArray> Ids; + Ids.Add(MakeShared(GenerateId())); + TSharedPtr Json = MakeShared(); + Json->SetArrayField(TEXT("user_ids"), Ids); + + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Json](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("status_follow"), Json); + }) + .Next([this, Done](FNakamaWebSocketResponse Resp) + { + WSS_FAIL_ON_ERROR(Resp, Done); + Done.Execute(); + }); + }); + + LatentIt("should follow and then unfollow a user ID", [this](const FDoneDelegate& Done) + { + FString FakeUserId = GenerateId(); + + TArray> FollowIds; + FollowIds.Add(MakeShared(FakeUserId)); + TSharedPtr FollowJson = MakeShared(); + FollowJson->SetArrayField(TEXT("user_ids"), FollowIds); + + WSSub->Connect(MakeConnParams(Session)) + .Next([this, FollowJson](FNakamaWebSocketConnectionResult CR) -> TNakamaFuture + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + return WSSub->Send(TEXT("status_follow"), FollowJson); + }) + .Next([this, FakeUserId](FNakamaWebSocketResponse FollowResp) -> TNakamaFuture + { + if (FollowResp.ErrorCode != ENakamaWebSocketError::None) + { + AddError(TEXT("status_follow failed")); + return MakeCompletedFuture(FollowResp); + } + TArray> UnfollowIds; + UnfollowIds.Add(MakeShared(FakeUserId)); + TSharedPtr UnfollowJson = MakeShared(); + UnfollowJson->SetArrayField(TEXT("user_ids"), UnfollowIds); + return WSSub->Send(TEXT("status_unfollow"), UnfollowJson); + }) + .Next([this, Done](FNakamaWebSocketResponse UnfollowResp) + { + WSS_FAIL_ON_ERROR(UnfollowResp, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// DELEGATE TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemDelegatesSpec, "IntegrationTests.NakamaWebSocketSubsystem.Delegates", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemDelegatesSpec) + +void FNakamaWebSocketSubsystemDelegatesSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("MessageSent", [this]() + { + LatentIt("should fire the MessageSent delegate when a message is dispatched", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + + // Use a shared handle so we can self-remove; a shared bool prevents + // double-completion if the delegate fires more than once during the test. + TSharedRef Handle = MakeShared(); + TSharedRef bFired = MakeShared(false); + + *Handle = WSSub->MessageSent.AddLambda( + [this, Done, Handle, bFired](const FString& Message) + { + if (*bFired) return; + *bFired = true; + TestFalse("MessageSent payload should not be empty", Message.IsEmpty()); + Done.Execute(); + }); + + // Trigger a send — we don't await the response future here. + WSSub->Send(TEXT("ping"), MakeShared()); + }); + }); + }); + + Describe("ServerResponseReceived", [this]() + { + LatentIt("should fire ServerResponseReceived when the server replies", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + + TSharedRef Handle = MakeShared(); + TSharedRef bFired = MakeShared(false); + + *Handle = WSSub->ServerResponseReceived.AddLambda( + [this, Done, Handle, bFired](const FString& Message) + { + if (*bFired) return; + *bFired = true; + TestFalse("ServerResponseReceived payload should not be empty", Message.IsEmpty()); + Done.Execute(); + }); + + WSSub->Send(TEXT("match_create"), MakeShared()); + }); + }); + + LatentIt("should fire ServerResponseReceived for every distinct response", [this](const FDoneDelegate& Done) + { + WSSub->Connect(MakeConnParams(Session)) + .Next([this, Done](FNakamaWebSocketConnectionResult CR) + { + TestFalse("Connect", CR.ErrorCode != ENakamaWebSocketError::None); + + constexpr int32 NumSends = 3; + TSharedRef> FireCount = MakeShared>(0); + TSharedRef Handle = MakeShared(); + + *Handle = WSSub->ServerResponseReceived.AddLambda( + [this, Done, Handle, FireCount](const FString&) + { + if (++(*FireCount) == NumSends) + { + Done.Execute(); + } + }); + + for (int32 i = 0; i < NumSends; ++i) + { + WSSub->Send(TEXT("match_create"), MakeShared()); + } + }); + }); + }); +} + +// ============================================================================ +// DATA RACE REGRESSION TEST +// ============================================================================ + +BEGIN_DEFINE_SPEC(FNakamaWebSocketSubsystemDataRaceSpec, + "IntegrationTests.NakamaWebSocketSubsystem.DataRace", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FNakamaClientConfig ClientConfig; + FNakamaSession Session; + UNakamaWebSocketSubsystem* WSSub = nullptr; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FNakamaWebSocketSubsystemDataRaceSpec) + +void FNakamaWebSocketSubsystemDataRaceSpec::Define() +{ + BeforeEach([this]() + { + ClientConfig = FNakamaClientConfig{ RtServerKey, RtHost, RtPort, false }; + WSSub = CreateTestSubsystem(); + WSSub->AddToRoot(); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Nakama::AuthenticateCustom(ClientConfig, GenerateId(), true, TEXT("")) + .Next([this, Done](FNakamaSessionResult R) + { + if (R.bIsError) { AddError(FString::Printf(TEXT("Auth: %s"), *R.Error.Message)); Done.Execute(); return; } + Session = R.Value; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (WSSub) + { + WSSub->Close(); + WSSub->RemoveFromRoot(); + WSSub = nullptr; + } + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Nakama::DeleteAccount(ClientConfig, Session).Next([Done](FNakamaVoidResult) { Done.Execute(); }); + }); + + Describe("bIsConnected", [this]() + { + LatentIt("should not crash when Send races with Connect on bIsConnected", + [this](const FDoneDelegate& Done) + { + // Stress test: Connect fires on the WS thread while the game thread + // hammers Send(). With std::atomic bIsConnected this is safe; + // with a plain bool it would be a C++11 data race (UB). + WSSub->Connect(MakeConnParams(Session)); + + constexpr int32 Iterations = 500; + for (int32 i = 0; i < Iterations; ++i) + { + WSSub->Send(TEXT("match_create"), MakeShared()); + FPlatformProcess::SleepNoStats(0.0f); + } + + WSSub->Close(); + Done.Execute(); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/ObjectOwnershipTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/ObjectOwnershipTests.cpp new file mode 100644 index 000000000..d57e1020c --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/ObjectOwnershipTests.cpp @@ -0,0 +1,112 @@ +/** + * Object Ownership Tests + * + * Demonstrates bug: factory functions in NakamaClientBlueprintLibrary.cpp and + * SatoriClientBlueprintLibrary.cpp call NewObject() without an Outer, so + * created async actions are stranded in GetTransientPackage() instead of being + * owned by the WorldContextObject. + * + * All tests in this file are EXPECTED TO FAIL until the bug is fixed. + * Fix: change NewObject() -> NewObject(WorldContextObject) in each factory. + */ + +#include "Misc/AutomationTest.h" +#include "NakamaClientBlueprintLibrary.h" +#include "SatoriClientBlueprintLibrary.h" +#include "UObject/Package.h" +#include "Engine/Engine.h" + +// ============================================================================ +// Nakama — async action outer must be WorldContextObject, not transient package +// ============================================================================ + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FNakamaAsyncActionOuterTest, + "IntegrationTests.ObjectOwnership.NakamaAsyncActionOuter", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter +) + +bool FNakamaAsyncActionOuterTest::RunTest(const FString& Parameters) +{ + // GEngine is a concrete, always-available UObject that is never GetTransientPackage(). + // UObject itself is abstract in UE5.5+ and cannot be directly instantiated. + UObject* WorldContext = GEngine; + + FNakamaClientConfig Client{TEXT("defaultkey"), TEXT("127.0.0.1"), 7350, false}; + FNakamaSession Session; + + // Create the action — do NOT call Activate() so no network I/O occurs. + UNakamaClientHealthcheck* Action = + UNakamaClientHealthcheck::Healthcheck(WorldContext, Client, Session); + + // BUG: NewObject() in the factory passes no Outer, + // so GetOuter() returns GetTransientPackage() instead of WorldContext. + // This test FAILS until the factory is fixed. + TestNotEqual( + TEXT("[BUG] Nakama async action outer is the transient package; expected WorldContextObject"), + Action->GetOuter(), + static_cast(GetTransientPackage()) + ); + + // Also verify we can walk up to the world context via the outer chain. + // GetTypedOuter returns nullptr when the outer chain doesn't pass through WorldContext. + bool bFoundWorldContext = false; + UObject* Outer = Action->GetOuter(); + while (Outer) + { + if (Outer == WorldContext) { bFoundWorldContext = true; break; } + Outer = Outer->GetOuter(); + } + TestTrue( + TEXT("[BUG] WorldContextObject not reachable through async action outer chain"), + bFoundWorldContext + ); + + return true; +} + +// ============================================================================ +// Satori — same bug in SatoriClientBlueprintLibrary factory functions +// ============================================================================ + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FSatoriAsyncActionOuterTest, + "IntegrationTests.ObjectOwnership.SatoriAsyncActionOuter", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter +) + +bool FSatoriAsyncActionOuterTest::RunTest(const FString& Parameters) +{ + UObject* WorldContext = GEngine; + + FSatoriClientConfig Client; + Client.Host = TEXT("127.0.0.1"); + Client.Port = 7480; + Client.ServerKey = TEXT("defaultkey"); + Client.bUseSSL = false; + + // Create the action — do NOT Activate(). + USatoriClientAuthenticate* Action = + USatoriClientAuthenticate::Authenticate(WorldContext, Client, TEXT("test-id"), false, {}, {}); + + // BUG: same pattern — no Outer passed to NewObject. + TestNotEqual( + TEXT("[BUG] Satori async action outer is the transient package; expected WorldContextObject"), + Action->GetOuter(), + static_cast(GetTransientPackage()) + ); + + bool bFoundWorldContext = false; + UObject* Outer = Action->GetOuter(); + while (Outer) + { + if (Outer == WorldContext) { bFoundWorldContext = true; break; } + Outer = Outer->GetOuter(); + } + TestTrue( + TEXT("[BUG] WorldContextObject not reachable through Satori async action outer chain"), + bFoundWorldContext + ); + + return true; +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/SatoriAsyncTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/SatoriAsyncTests.cpp new file mode 100644 index 000000000..220e2443a --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/SatoriAsyncTests.cpp @@ -0,0 +1,974 @@ +/** + * Satori Async API Integration Test Suite + * + * Tests for all Satori REST API endpoints using the TFuture-based Satori:: free-function API. + * Mirrors the Nakama async test pattern: each spec authenticates in BeforeEach, then tests + * individual endpoints. + */ + +#include "Satori.h" +#include "Misc/Guid.h" +#include "SatoriConsoleHelper.h" + +/** + * Helper macro: early-return on unexpected error inside a .Next() callback. + */ +#define SATORI_FAIL_ON_ERROR(Result, Done) \ + if (Result.bIsError) { \ + AddError(FString::Printf(TEXT("Unexpected error %d: %s"), Result.Error.Code, *Result.Error.Message)); \ + Done.Execute(); \ + return; \ + } + +// ============================================================================ +// AUTHENTICATION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncAuthSpec, "IntegrationTests.Satori.Auth", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FString TestIdentityId; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncAuthSpec) + +FString FSatoriAsyncAuthSpec::ServerKey; +const FString FSatoriAsyncAuthSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncAuthSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + TestIdentityId = GenerateId(); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("Authenticate", [this]() + { + LatentIt("should authenticate with valid identity ID", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TestIdentityId, false, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + TestTrue("Session has refresh token", !Result.Value.RefreshToken.IsEmpty()); + Session = Result.Value; + Done.Execute(); + }); + }); + + LatentIt("should authenticate with no_session flag", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TestIdentityId, true, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("Token is empty with no_session", Result.Value.Token.IsEmpty()); + Done.Execute(); + }); + }); + + LatentIt("should authenticate with default properties", [this](const FDoneDelegate& Done) + { + TMap Defaults; + Defaults.Add(TEXT("platform"), TEXT("windows")); + Defaults.Add(TEXT("version"), TEXT("1.0")); + + Satori::Authenticate(ClientConfig, TestIdentityId, false, Defaults, {}).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + Session = Result.Value; + Done.Execute(); + }); + }); + + LatentIt("should authenticate with custom properties", [this](const FDoneDelegate& Done) + { + TMap Custom; + Custom.Add(TEXT("preferred_mode"), TEXT("ranked")); + + Satori::Authenticate(ClientConfig, TestIdentityId, false, {}, Custom).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("Session has token", !Result.Value.Token.IsEmpty()); + Session = Result.Value; + Done.Execute(); + }); + }); + + LatentIt("should fail with empty identity ID", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TEXT(""), false, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + TestTrue("Expected error", Result.bIsError); + Done.Execute(); + }); + }); + + LatentIt("should fail with identity ID too short", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TEXT("abc"), false, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + TestTrue("Expected error", Result.bIsError); + Done.Execute(); + }); + }); + }); + + Describe("AuthenticateRefresh", [this]() + { + LatentIt("should refresh a valid session", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TestIdentityId, false, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Session = Result.Value; + const FString RefreshToken = Result.Value.RefreshToken; + + Satori::AuthenticateRefresh(ClientConfig, RefreshToken).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("Refreshed session has token", !Result.Value.Token.IsEmpty()); + TestTrue("Refreshed session has refresh token", !Result.Value.RefreshToken.IsEmpty()); + Done.Execute(); + }); + }); + }); + + LatentIt("should fail with invalid refresh token", [this](const FDoneDelegate& Done) + { + Satori::AuthenticateRefresh(ClientConfig, TEXT("invalid-token")).Next([this, Done](FSatoriSessionResult Result) + { + TestTrue("Expected error", Result.bIsError); + Done.Execute(); + }); + }); + }); + + Describe("AuthenticateLogout", [this]() + { + LatentIt("should log out a valid session", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, TestIdentityId, false, {}, {}).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Session = Result.Value; + const FString Token = Result.Value.Token; + const FString RefreshToken = Result.Value.RefreshToken; + + Satori::AuthenticateLogout(ClientConfig, Token, RefreshToken).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); + }); +} + +// ============================================================================ +// HEALTHCHECK TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncHealthcheckSpec, "IntegrationTests.Satori.Healthcheck", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + +END_DEFINE_SPEC(FSatoriAsyncHealthcheckSpec) + +FString FSatoriAsyncHealthcheckSpec::ServerKey; +const FString FSatoriAsyncHealthcheckSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncHealthcheckSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + LatentIt("should return healthy", [this](const FDoneDelegate& Done) + { + Satori::Healthcheck(ClientConfig, FSatoriSession{}).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("readycheck should return ready", [this](const FDoneDelegate& Done) + { + Satori::Readycheck(ClientConfig, FSatoriSession{}).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); +} + +// ============================================================================ +// IDENTITY TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncIdentitySpec, "IntegrationTests.Satori.Identity", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncIdentitySpec) + +FString FSatoriAsyncIdentitySpec::ServerKey; +const FString FSatoriAsyncIdentitySpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncIdentitySpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("Identify", [this]() + { + LatentIt("should enrich session with a new identity", [this](const FDoneDelegate& Done) + { + const FString InitialId = GenerateId(); + const FString NewId = GenerateId(); + + Satori::Authenticate(ClientConfig, InitialId, false, {}, {}).Next([this, NewId](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::Identify(ClientConfig, Session, NewId, {}, {}); + }).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestTrue("New session has token", !Result.Value.Token.IsEmpty()); + Done.Execute(); + }); + }); + }); + + Describe("DeleteIdentity", [this]() + { + LatentIt("should delete an identity", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + + Satori::Authenticate(ClientConfig, Id, false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::DeleteIdentity(ClientConfig, Session); + }).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// PROPERTIES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncPropertiesSpec, "IntegrationTests.Satori.Properties", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncPropertiesSpec) + +FString FSatoriAsyncPropertiesSpec::ServerKey; +const FString FSatoriAsyncPropertiesSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncPropertiesSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("ListProperties", [this]() + { + LatentIt("should list properties for authenticated identity", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + TMap Defaults; + Defaults.Add(TEXT("platform"), TEXT("windows")); + + Satori::Authenticate(ClientConfig, Id, false, Defaults, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::ListProperties(ClientConfig, Session); + }).Next([this, Done](FSatoriPropertiesResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + const auto& Props = Result.Value; + TestTrue("Default properties contain platform", Props.Default.Contains(TEXT("platform"))); + Done.Execute(); + }); + }); + }); + + Describe("UpdateProperties", [this]() + { + LatentIt("should update custom properties", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + + Satori::Authenticate(ClientConfig, Id, false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + + TMap Custom; + Custom.Add(TEXT("level"), TEXT("5")); + Custom.Add(TEXT("rank"), TEXT("gold")); + return Satori::UpdateProperties(ClientConfig, Session, false, {}, Custom); + }).Next([this](const FSatoriVoid&) + { + return Satori::ListProperties(ClientConfig, Session); + }).Next([this, Done](FSatoriPropertiesResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + const auto& Props = Result.Value; + TestEqual("Custom level", Props.Custom.FindRef(TEXT("level")), TEXT("5")); + TestEqual("Custom rank", Props.Custom.FindRef(TEXT("rank")), TEXT("gold")); + Done.Execute(); + }); + }); + + LatentIt("should update default properties", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + + Satori::Authenticate(ClientConfig, Id, false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + + TMap Defaults; + Defaults.Add(TEXT("region"), TEXT("eu-west")); + return Satori::UpdateProperties(ClientConfig, Session, false, Defaults, {}); + }).Next([this](const FSatoriVoid&) + { + return Satori::ListProperties(ClientConfig, Session); + }).Next([this, Done](FSatoriPropertiesResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestEqual("Default region", Result.Value.Default.FindRef(TEXT("region")), TEXT("eu-west")); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// EVENT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncEventSpec, "IntegrationTests.Satori.Event", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncEventSpec) + +FString FSatoriAsyncEventSpec::ServerKey; +const FString FSatoriAsyncEventSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncEventSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("Event", [this]() + { + LatentIt("should publish a single event", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + + FSatoriEvent Evt; + Evt.Name = TEXT("game_start"); + Evt.Value = TEXT("tutorial"); + + return Satori::Event(ClientConfig, Session, {Evt}); + }).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("should publish multiple events", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + + FSatoriEvent Evt1; + Evt1.Name = TEXT("game_start"); + Evt1.Value = TEXT("pvp"); + + FSatoriEvent Evt2; + Evt2.Name = TEXT("level_complete"); + Evt2.Value = TEXT("3"); + Evt2.Metadata.Add(TEXT("score"), TEXT("1500")); + + return Satori::Event(ClientConfig, Session, {Evt1, Evt2}); + }).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("should publish event with metadata", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + + FSatoriEvent Evt; + Evt.Name = TEXT("purchase"); + Evt.Value = TEXT("gem_pack_100"); + Evt.Metadata.Add(TEXT("currency"), TEXT("USD")); + Evt.Metadata.Add(TEXT("amount"), TEXT("9.99")); + + return Satori::Event(ClientConfig, Session, {Evt}); + }).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// FLAGS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncFlagsSpec, "IntegrationTests.Satori.Flags", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncFlagsSpec) + +FString FSatoriAsyncFlagsSpec::ServerKey; +const FString FSatoriAsyncFlagsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncFlagsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("GetFlags", [this]() + { + LatentIt("should list all flags", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetFlags(ClientConfig, Session, {}, {}); + }).Next([this, Done](FSatoriFlagListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + // Fresh server may have no flags configured, but the call should succeed + Done.Execute(); + }); + }); + + LatentIt("should filter flags by name", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetFlags(ClientConfig, Session, {TEXT("nonexistent_flag")}, {}); + }).Next([this, Done](FSatoriFlagListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestEqual("No flags returned for nonexistent name", Result.Value.Flags.Num(), 0); + Done.Execute(); + }); + }); + }); + + Describe("GetFlagOverrides", [this]() + { + LatentIt("should list all flag overrides", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetFlagOverrides(ClientConfig, Session, {}, {}); + }).Next([this, Done](FSatoriFlagOverrideListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// EXPERIMENTS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncExperimentsSpec, "IntegrationTests.Satori.Experiments", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncExperimentsSpec) + +FString FSatoriAsyncExperimentsSpec::ServerKey; +const FString FSatoriAsyncExperimentsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncExperimentsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("GetExperiments", [this]() + { + LatentIt("should list all experiments", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetExperiments(ClientConfig, Session, {}, {}); + }).Next([this, Done](FSatoriExperimentListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("should filter experiments by name", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetExperiments(ClientConfig, Session, {TEXT("nonexistent_experiment")}, {}); + }).Next([this, Done](FSatoriExperimentListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestEqual("No experiments for nonexistent name", Result.Value.Experiments.Num(), 0); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// LIVE EVENTS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncLiveEventsSpec, "IntegrationTests.Satori.LiveEvents", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncLiveEventsSpec) + +FString FSatoriAsyncLiveEventsSpec::ServerKey; +const FString FSatoriAsyncLiveEventsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncLiveEventsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("GetLiveEvents", [this]() + { + LatentIt("should list all live events", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetLiveEvents(ClientConfig, Session, {}, {}, 0, 0, 0, 0); + }).Next([this, Done](FSatoriLiveEventListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + + LatentIt("should filter live events by name", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetLiveEvents(ClientConfig, Session, {TEXT("nonexistent_event")}, {}, 0, 0, 0, 0); + }).Next([this, Done](FSatoriLiveEventListResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + TestEqual("No live events for nonexistent name", Result.Value.LiveEvents.Num(), 0); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// MESSAGES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncMessagesSpec, "IntegrationTests.Satori.Messages", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncMessagesSpec) + +FString FSatoriAsyncMessagesSpec::ServerKey; +const FString FSatoriAsyncMessagesSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncMessagesSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Session = FSatoriSession{}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("GetMessageList", [this]() + { + LatentIt("should list messages for identity", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetMessageList(ClientConfig, Session, 10, true, TEXT(""), {}); + }).Next([this, Done](FSatoriGetMessageListResponseResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + // Fresh identity has no messages + TestEqual("No messages for fresh identity", Result.Value.Messages.Num(), 0); + Done.Execute(); + }); + }); + + LatentIt("should list messages in reverse order", [this](const FDoneDelegate& Done) + { + Satori::Authenticate(ClientConfig, GenerateId(), false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetMessageList(ClientConfig, Session, 10, false, TEXT(""), {}); + }).Next([this, Done](FSatoriGetMessageListResponseResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// SESSION LIFECYCLE TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriAsyncSessionSpec, "IntegrationTests.Satori.Session", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriRetryConfig RetryConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriAsyncSessionSpec) + +FString FSatoriAsyncSessionSpec::ServerKey; +const FString FSatoriAsyncSessionSpec::Host = TEXT("127.0.0.1"); + +void FSatoriAsyncSessionSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + Done.Execute(); + }); + }); + + BeforeEach([this]() + { + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) { Done.Execute(); return; } + Satori::DeleteIdentity(ClientConfig, Session).Next([Done](FSatoriVoidResult) { Done.Execute(); }); + }); + + Describe("Full lifecycle", [this]() + { + LatentIt("should authenticate, use API, refresh, and logout", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + + Satori::Authenticate(ClientConfig, Id, false, {}, {}).Next([this](const FSatoriSession& Sess) + { + Session = Sess; + return Satori::GetFlags(ClientConfig, Session, {}, {}); + }).Next([this](const FSatoriFlagList&) + { + return Satori::AuthenticateRefresh(ClientConfig, Session.RefreshToken); + }).Next([this, Done](FSatoriSessionResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + const FString Token = Result.Value.Token; + const FString RefreshToken = Result.Value.RefreshToken; + + Satori::AuthenticateLogout(ClientConfig, Token, RefreshToken).Next([this, Done](FSatoriVoidResult Result) + { + SATORI_FAIL_ON_ERROR(Result, Done); + Done.Execute(); + }); + }); + }); + }); + + Describe("Expired session", [this]() + { + LatentIt("should fail API call with invalid bearer token", [this](const FDoneDelegate& Done) + { + FSatoriSession FakeSession; + FakeSession.Token = TEXT("invalid.bearer.token"); + FakeSession.RefreshToken = TEXT("invalid.refresh.token"); + + Satori::GetFlags(ClientConfig, FakeSession, {}, {}).Next([this, Done](FSatoriFlagListResult Result) + { + TestTrue("Expected error with invalid session", Result.bIsError); + Done.Execute(); + }); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/SatoriBlueprintTests.cpp b/IntegrationTests/Source/IntegrationTests/Tests/SatoriBlueprintTests.cpp new file mode 100644 index 000000000..761f2605a --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/SatoriBlueprintTests.cpp @@ -0,0 +1,977 @@ +/** + * Satori Blueprint Async Action Test Suite + * + * Tests for SatoriBlueprints module UBlueprintAsyncActionBase subclasses. + * Each test fires a BP async action, then verifies the server-side effect + * through the C++ client (which supports lambda callbacks natively). + * + * Mirrors the Nakama BP test pattern from NakamaBlueprintTests.cpp. + */ + +#include "Misc/AutomationTest.h" +#include "SatoriApi.h" +#include "SatoriClientBlueprintLibrary.h" +#include "Misc/Guid.h" +#include "Containers/Ticker.h" +#include "SatoriConsoleHelper.h" + +// Helper: keep the BP action alive for the duration of the async call (tests pass +// nullptr as WorldContextObject so RegisterWithGameInstance is a no-op), then run +// verification once the action completes. +// The base class constructor sets RF_StrongRefOnFrame, and SetReadyToDestroy() +// clears it. We AddToRoot() to prevent GC across frames, then poll until +// RF_StrongRefOnFrame is cleared (signalling the action called SetReadyToDestroy). +static void VerifyWhenComplete(UBlueprintAsyncActionBase* Action, TFunction VerifyFunc) +{ + Action->AddToRoot(); + + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([Action, VerifyFunc = MoveTemp(VerifyFunc)](float) -> bool + { + if (Action->HasAnyFlags(RF_StrongRefOnFrame)) + { + return true; // keep ticking — action hasn't called SetReadyToDestroy yet + } + Action->RemoveFromRoot(); + VerifyFunc(); + return false; + }), + 0.0f + ); +} + +// ============================================================================ +// HEALTHCHECK TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPHealthcheckSpec, "IntegrationTests.SatoriBlueprint.Healthcheck", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriSession Session; + FSatoriClientConfig ClientConfig; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPHealthcheckSpec) + +FString FSatoriBPHealthcheckSpec::ServerKey; +const FString FSatoriBPHealthcheckSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPHealthcheckSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("Healthcheck", [this]() + { + LatentIt("should pass healthcheck", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientHealthcheck::Healthcheck(nullptr, ClientConfig, Session); + Action->Activate(); + + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::Healthcheck(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("Readycheck", [this]() + { + LatentIt("should pass readycheck", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientReadycheck::Readycheck(nullptr, ClientConfig, Session); + Action->Activate(); + + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::Readycheck(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Readycheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// AUTHENTICATION TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPAuthSpec, "IntegrationTests.SatoriBlueprint.Auth", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPAuthSpec) + +FString FSatoriBPAuthSpec::ServerKey; +const FString FSatoriBPAuthSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPAuthSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + Session = FSatoriSession(); + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + if (Session.Token.IsEmpty()) + { + Done.Execute(); + return; + } + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("Authenticate", [this]() + { + LatentIt("should authenticate with valid identity ID", [this](const FDoneDelegate& Done) + { + const FString Id = GenerateId(); + + auto* Action = USatoriClientAuthenticate::Authenticate( + nullptr, ClientConfig, Id, false, {}, {}); + Action->Activate(); + + // Verify: re-auth with the same ID should succeed + VerifyWhenComplete(Action, [this, Done, Id]() + { + SatoriApi::Authenticate(ClientConfig, Id, false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + TestTrue("Session has token", !Result.Token.IsEmpty()); + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Re-auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("AuthenticateRefresh", [this]() + { + LatentIt("should refresh a session via BP action", [this](const FDoneDelegate& Done) + { + // Setup: authenticate via C++ to get a refresh token + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + auto* Action = USatoriClientAuthenticateRefresh::AuthenticateRefresh( + nullptr, ClientConfig, Result.RefreshToken); + Action->Activate(); + + // Verify: server still responds to healthcheck + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::Healthcheck(ClientConfig, FSatoriSession{}, + [Done]() { Done.Execute(); }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + + Describe("AuthenticateLogout", [this]() + { + LatentIt("should logout a session via BP action", [this](const FDoneDelegate& Done) + { + // Setup: authenticate via C++ to get tokens + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + auto* Action = USatoriClientAuthenticateLogout::AuthenticateLogout( + nullptr, ClientConfig, Result.Token, Result.RefreshToken); + Action->Activate(); + + // Verify: action completed; server still responds + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::Healthcheck(ClientConfig, FSatoriSession{}, + [Done]() { Done.Execute(); }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); +} + +// ============================================================================ +// IDENTITY TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPIdentitySpec, "IntegrationTests.SatoriBlueprint.Identity", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPIdentitySpec) + +FString FSatoriBPIdentitySpec::ServerKey; +const FString FSatoriBPIdentitySpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPIdentitySpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("Identify", [this]() + { + LatentIt("should identify with a new identity", [this](const FDoneDelegate& Done) + { + const FString NewId = GenerateId(); + + auto* Action = USatoriClientIdentify::Identify( + nullptr, ClientConfig, Session, NewId, {}, {}); + Action->Activate(); + + // Verify: action completed (Identify returns a new session, old one is invalid) + VerifyWhenComplete(Action, [this, Done]() + { + Done.Execute(); + }); + }); + }); + + Describe("DeleteIdentity", [this]() + { + LatentIt("should delete an identity", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientDeleteIdentity::DeleteIdentity( + nullptr, ClientConfig, Session); + Action->Activate(); + + // Verify: action completed without crash + VerifyWhenComplete(Action, [this, Done]() + { + Done.Execute(); + }); + }); + }); +} + +// ============================================================================ +// PROPERTIES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPPropertiesSpec, "IntegrationTests.SatoriBlueprint.Properties", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPPropertiesSpec) + +FString FSatoriBPPropertiesSpec::ServerKey; +const FString FSatoriBPPropertiesSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPPropertiesSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("ListProperties", [this]() + { + LatentIt("should list properties for authenticated identity", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientListProperties::ListProperties( + nullptr, ClientConfig, Session); + Action->Activate(); + + // Verify via C++ ListProperties + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::ListProperties(ClientConfig, Session, + [this, Done](const FSatoriProperties&) + { + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("ListProperties failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UpdateProperties", [this]() + { + LatentIt("should update custom properties via BP action", [this](const FDoneDelegate& Done) + { + TMap Custom; + Custom.Add(TEXT("level"), TEXT("5")); + + auto* Action = USatoriClientUpdateProperties::UpdateProperties( + nullptr, ClientConfig, Session, false, {}, Custom); + Action->Activate(); + + // Verify: property was persisted + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::ListProperties(ClientConfig, Session, + [this, Done](const FSatoriProperties& Props) + { + TestEqual("Custom level", Props.Custom.FindRef(TEXT("level")), TEXT("5")); + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("ListProperties failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// EVENT TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPEventSpec, "IntegrationTests.SatoriBlueprint.Event", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPEventSpec) + +FString FSatoriBPEventSpec::ServerKey; +const FString FSatoriBPEventSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPEventSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("Event", [this]() + { + LatentIt("should publish an event via BP action", [this](const FDoneDelegate& Done) + { + FSatoriEvent Evt; + Evt.Name = TEXT("game_start"); + Evt.Value = TEXT("tutorial"); + + auto* Action = USatoriClientEvent::Event( + nullptr, ClientConfig, Session, {Evt}); + Action->Activate(); + + // Verify: action completed; server still responds + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::Healthcheck(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Healthcheck failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// FLAGS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPFlagsSpec, "IntegrationTests.SatoriBlueprint.Flags", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPFlagsSpec) + +FString FSatoriBPFlagsSpec::ServerKey; +const FString FSatoriBPFlagsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPFlagsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("GetFlags", [this]() + { + LatentIt("should list all flags via BP action", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientGetFlags::GetFlags( + nullptr, ClientConfig, Session, {}, {}); + Action->Activate(); + + // Verify via C++ GetFlags + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::GetFlags(ClientConfig, Session, {}, {}, + [this, Done](const FSatoriFlagList&) + { + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("GetFlags failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("GetFlagOverrides", [this]() + { + LatentIt("should list all flag overrides via BP action", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientGetFlagOverrides::GetFlagOverrides( + nullptr, ClientConfig, Session, {}, {}); + Action->Activate(); + + // Verify via C++ GetFlagOverrides + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::GetFlagOverrides(ClientConfig, Session, {}, {}, + [this, Done](const FSatoriFlagOverrideList&) + { + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("GetFlagOverrides failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// EXPERIMENTS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPExperimentsSpec, "IntegrationTests.SatoriBlueprint.Experiments", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPExperimentsSpec) + +FString FSatoriBPExperimentsSpec::ServerKey; +const FString FSatoriBPExperimentsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPExperimentsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("GetExperiments", [this]() + { + LatentIt("should list all experiments via BP action", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientGetExperiments::GetExperiments( + nullptr, ClientConfig, Session, {}, {}); + Action->Activate(); + + // Verify via C++ GetExperiments + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::GetExperiments(ClientConfig, Session, {}, {}, + [this, Done](const FSatoriExperimentList&) + { + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("GetExperiments failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// LIVE EVENTS TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPLiveEventsSpec, "IntegrationTests.SatoriBlueprint.LiveEvents", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPLiveEventsSpec) + +FString FSatoriBPLiveEventsSpec::ServerKey; +const FString FSatoriBPLiveEventsSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPLiveEventsSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("GetLiveEvents", [this]() + { + LatentIt("should list all live events via BP action", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientGetLiveEvents::GetLiveEvents( + nullptr, ClientConfig, Session, {}, {}, 0, 0, 0, 0); + Action->Activate(); + + // Verify via C++ GetLiveEvents + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::GetLiveEvents(ClientConfig, Session, {}, {}, 0, 0, 0, 0, + [this, Done](const FSatoriLiveEventList&) + { + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("GetLiveEvents failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); +} + +// ============================================================================ +// MESSAGES TESTS +// ============================================================================ + +BEGIN_DEFINE_SPEC(FSatoriBPMessagesSpec, "IntegrationTests.SatoriBlueprint.Messages", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) + + FSatoriClientConfig ClientConfig; + FSatoriSession Session; + + static FString ServerKey; + static const FString Host; + static constexpr int32 Port = 7450; + + FString GenerateId() { return FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); } + +END_DEFINE_SPEC(FSatoriBPMessagesSpec) + +FString FSatoriBPMessagesSpec::ServerKey; +const FString FSatoriBPMessagesSpec::Host = TEXT("127.0.0.1"); + +void FSatoriBPMessagesSpec::Define() +{ + LatentBeforeEach([this](const FDoneDelegate& Done) + { + GetSatoriApiKey().Next([this, Done](FString Key) + { + ServerKey = Key; + ClientConfig = FSatoriClientConfig{ServerKey, Host, Port, false}; + Done.Execute(); + }); + }); + + LatentBeforeEach([this](const FDoneDelegate& Done) + { + SatoriApi::Authenticate(ClientConfig, GenerateId(), false, {}, {}, + [this, Done](const FSatoriSession& Result) + { + Session = Result; + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("Setup auth failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + + LatentAfterEach([this](const FDoneDelegate& Done) + { + SatoriApi::DeleteIdentity(ClientConfig, Session, + [Done]() { Done.Execute(); }, + [Done](const FSatoriError&) { Done.Execute(); } + ); + }); + + Describe("GetMessageList", [this]() + { + LatentIt("should list messages for identity via BP action", [this](const FDoneDelegate& Done) + { + auto* Action = USatoriClientGetMessageList::GetMessageList( + nullptr, ClientConfig, Session, 10, true, TEXT(""), {}); + Action->Activate(); + + // Verify via C++ GetMessageList + VerifyWhenComplete(Action, [this, Done]() + { + SatoriApi::GetMessageList(ClientConfig, Session, 10, true, TEXT(""), {}, + [this, Done](const FSatoriGetMessageListResponse& Result) + { + TestEqual("No messages for fresh identity", Result.Messages.Num(), 0); + Done.Execute(); + }, + [this, Done](const FSatoriError& Error) + { + AddError(FString::Printf(TEXT("GetMessageList failed: %s"), *Error.Message)); + Done.Execute(); + } + ); + }); + }); + }); + + Describe("UpdateMessage", [this]() + { + LatentIt("should update a message via BP action", [this](const FDoneDelegate& Done) + { + // UpdateMessage with a nonexistent ID should still not crash the action + auto* Action = USatoriClientUpdateMessage::UpdateMessage( + nullptr, ClientConfig, Session, TEXT("nonexistent-id"), 0, 0); + Action->Activate(); + + // Verify: action completed (may error, but should not crash) + VerifyWhenComplete(Action, [this, Done]() + { + Done.Execute(); + }); + }); + }); + + Describe("DeleteMessage", [this]() + { + LatentIt("should delete a message via BP action", [this](const FDoneDelegate& Done) + { + // DeleteMessage with a nonexistent ID should still not crash the action + auto* Action = USatoriClientDeleteMessage::DeleteMessage( + nullptr, ClientConfig, Session, TEXT("nonexistent-id")); + Action->Activate(); + + // Verify: action completed (may error, but should not crash) + VerifyWhenComplete(Action, [this, Done]() + { + Done.Execute(); + }); + }); + }); +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.cpp b/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.cpp new file mode 100644 index 000000000..6654746b0 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.cpp @@ -0,0 +1,127 @@ +#include "SatoriConsoleHelper.h" + +#include "Misc/Guid.h" +#include "HttpModule.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" + +static FString GCachedApiKey; + +// Makes a Console REST request. OnComplete is always called; Json is null on failure. +static void MakeConsoleRequest( + const FString& Url, + const FString& Method, + const FString& BearerToken, + const FString& Body, + TFunction)> OnComplete) +{ + auto Request = FHttpModule::Get().CreateRequest(); + Request->SetURL(Url); + Request->SetVerb(Method); + Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + if (!BearerToken.IsEmpty()) + { + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Bearer %s"), *BearerToken)); + } + if (!Body.IsEmpty()) + { + Request->SetContentAsString(Body); + } + Request->OnProcessRequestComplete().BindLambda( + [OnComplete](FHttpRequestPtr, FHttpResponsePtr Res, bool bSuccess) + { + TSharedPtr Json; + if (bSuccess && Res.IsValid()) + { + TSharedRef> Reader = TJsonReaderFactory<>::Create(Res->GetContentAsString()); + FJsonSerializer::Deserialize(Reader, Json); + } + OnComplete(Json); + }); + Request->ProcessRequest(); +} + +// Seeds event and property configurations sequentially (ignores errors, they may already exist). +// Resolves the promise with KeyValue when all steps complete. +static void SeedConfigAndFinish( + const FString& Token, + const FString& KeyValue, + TSharedPtr> Promise, + TArray> Steps, + int32 Index) +{ + if (Index >= Steps.Num()) + { + GCachedApiKey = KeyValue; + Promise->SetValue(KeyValue); + return; + } + + const FString Url = FString::Printf(TEXT("http://127.0.0.1:7451%s"), *Steps[Index].Key); + MakeConsoleRequest(Url, TEXT("POST"), Token, Steps[Index].Value, + [Token, KeyValue, Promise, Steps, Index](TSharedPtr) + { + SeedConfigAndFinish(Token, KeyValue, Promise, Steps, Index + 1); + }); +} + +static void GetKeyThenSeed(const FString& Token, const FString& KeyValue, TSharedPtr> Promise) +{ + const FString EventBody = TEXT("{\"value_schema_id\":\"00000000-0000-0000-0000-000000000002\",\"metadata_schema_id\":\"00000000-0000-0000-0000-000000000001\""); + const FString PropBody = TEXT("{\"schema_id\":\"00000000-0000-0000-0000-000000000002\""); + + TArray> Steps = { + {TEXT("/v1/console/event"), FString::Printf(TEXT("{\"name\":\"game_start\",%s}"), *EventBody.Mid(1))}, + {TEXT("/v1/console/event"), FString::Printf(TEXT("{\"name\":\"level_complete\",%s}"), *EventBody.Mid(1))}, + {TEXT("/v1/console/event"), FString::Printf(TEXT("{\"name\":\"purchase\",%s}"), *EventBody.Mid(1))}, + {TEXT("/v1/console/property"), FString::Printf(TEXT("{\"name\":\"level\",%s}"), *PropBody.Mid(1))}, + {TEXT("/v1/console/property"), FString::Printf(TEXT("{\"name\":\"rank\",%s}"), *PropBody.Mid(1))}, + {TEXT("/v1/console/property"), FString::Printf(TEXT("{\"name\":\"preferred_mode\",%s}"), *PropBody.Mid(1))}, + }; + SeedConfigAndFinish(Token, KeyValue, Promise, MoveTemp(Steps), 0); +} + +TFuture GetSatoriApiKey() +{ + TSharedPtr> Promise = MakeShared>(); + TFuture Future = Promise->GetFuture(); + + if (!GCachedApiKey.IsEmpty()) + { + Promise->SetValue(GCachedApiKey); + return Future; + } + + // Step 1: Authenticate with the Console API admin user. + MakeConsoleRequest( + TEXT("http://127.0.0.1:7451/v1/console/authenticate"), + TEXT("POST"), TEXT(""), + TEXT("{\"email\":\"admin@satoriserver.com\",\"password\":\"satoripassword\"}"), + [Promise](TSharedPtr Json) + { + FString Token; + if (!Json.IsValid() || !Json->TryGetStringField(TEXT("token"), Token) || Token.IsEmpty()) + { + Promise->SetValue(TEXT("")); + return; + } + + // Step 2: Create a unique key for this test session. + // Each shard runs as a separate process with its own GCachedApiKey, so using a + // shared key (list + rotate) causes race conditions. A unique name per shard avoids conflicts. + const FString KeyName = FGuid::NewGuid().ToString(EGuidFormats::Digits).ToLower(); + MakeConsoleRequest(TEXT("http://127.0.0.1:7451/v1/console/apikey"), TEXT("POST"), Token, + FString::Printf(TEXT("{\"name\":\"%s\"}"), *KeyName), + [Promise, Token](TSharedPtr Json) + { + FString KeyValue; + if (Json.IsValid()) Json->TryGetStringField(TEXT("value"), KeyValue); + GetKeyThenSeed(Token, KeyValue, Promise); + }); + }); + + return Future; +} diff --git a/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.h b/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.h new file mode 100644 index 000000000..799c07abd --- /dev/null +++ b/IntegrationTests/Source/IntegrationTests/Tests/SatoriConsoleHelper.h @@ -0,0 +1,12 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Async/Future.h" + +/** + * Fetches the Satori API key from the Console REST API. + * + * On first call: authenticates as admin, lists existing keys (creating one if none exist), + * and caches the result. Subsequent calls return the cached value immediately. + */ +TFuture GetSatoriApiKey(); diff --git a/IntegrationTests/Source/IntegrationTestsEditor.Target.cs b/IntegrationTests/Source/IntegrationTestsEditor.Target.cs new file mode 100644 index 000000000..a3d731156 --- /dev/null +++ b/IntegrationTests/Source/IntegrationTestsEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright 2023 The Nakama Authors. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class IntegrationTestsEditorTarget : TargetRules +{ + public IntegrationTestsEditorTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V6; + IncludeOrderVersion = EngineIncludeOrderVersion.Latest; + ExtraModuleNames.AddRange( new string[] { "IntegrationTests" } ); + } +} diff --git a/IntegrationTests/server/Dockerfile b/IntegrationTests/server/Dockerfile new file mode 100644 index 000000000..ab977e1ac --- /dev/null +++ b/IntegrationTests/server/Dockerfile @@ -0,0 +1,17 @@ +ARG NAKAMA_VERSION=3.37.0 + +FROM docker.io/heroiclabs/nakama-pluginbuilder:${NAKAMA_VERSION} AS builder + +ENV GO111MODULE=on +ENV CGO_ENABLED=1 + +WORKDIR /backend +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN go build -buildmode=plugin -trimpath -o /backend/backend.so + +FROM docker.io/heroiclabs/nakama:${NAKAMA_VERSION} + +COPY --from=builder /backend/backend.so /nakama/data/modules/backend.so diff --git a/IntegrationTests/server/docker-compose.yml b/IntegrationTests/server/docker-compose.yml new file mode 100644 index 000000000..9f329900d --- /dev/null +++ b/IntegrationTests/server/docker-compose.yml @@ -0,0 +1,82 @@ +services: + postgres: + container_name: unreal_integration_test_postgres + image: timescale/timescaledb-ha:pg18.1-ts2.23.1-all + platform: "linux/amd64" + environment: + - POSTGRES_DB=satori + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=localdb + expose: + - "5432" + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres", "-d", "satori"] + interval: 3s + timeout: 3s + retries: 5 + ports: + - "5432:5432" + volumes: + - data:/var/lib/postgresql/data + + nakama: + container_name: unreal_integration_test_nakama + build: + context: . + dockerfile: Dockerfile + depends_on: + postgres: + condition: service_healthy + entrypoint: + - "/bin/sh" + - "-ecx" + - > + /nakama/nakama migrate up --database.address postgres:localdb@postgres:5432/nakama && + exec /nakama/nakama --database.address postgres:localdb@postgres:5432/nakama + expose: + - "7349" + - "7350" + - "7351" + healthcheck: + test: ["CMD", "/nakama/nakama", "healthcheck"] + interval: 10s + timeout: 5s + retries: 5 + links: + - "postgres:db" + ports: + - "7349:7349" + - "7350:7350" + - "7351:7351" + restart: unless-stopped + + satori: + container_name: unreal_integration_test_satori + build: + context: .. + dockerfile: satori/Dockerfile + depends_on: + postgres: + condition: service_healthy + entrypoint: + - "/bin/sh" + - "-ecx" + - > + /satori/satori migrate up --database.address postgres:localdb@db:5432/satori && + exec /satori/satori --database.address postgres:localdb@db:5432/satori --cache_watcher.debounce_refresh_millisec=0 + ports: + - "7448:7448" + - "7449:7449" + - "7450:7450" + - "7451:7451" + healthcheck: + test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:7450/healthcheck"] + interval: 10s + timeout: 5s + retries: 5 + links: + - "postgres:db" + restart: unless-stopped + +volumes: + data: diff --git a/IntegrationTests/server/go.mod b/IntegrationTests/server/go.mod new file mode 100644 index 000000000..6fd896f82 --- /dev/null +++ b/IntegrationTests/server/go.mod @@ -0,0 +1,7 @@ +module nakama-unreal/nakama-server + +go 1.25.5 + +require github.com/heroiclabs/nakama-common v1.44.2 + +require google.golang.org/protobuf v1.36.11 // indirect diff --git a/IntegrationTests/server/go.sum b/IntegrationTests/server/go.sum new file mode 100644 index 000000000..eac5261e8 --- /dev/null +++ b/IntegrationTests/server/go.sum @@ -0,0 +1,6 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/heroiclabs/nakama-common v1.44.2 h1:aynrv/lFSI/1N54JxQ81FD80Iw7OL1yLrySwUemOOBU= +github.com/heroiclabs/nakama-common v1.44.2/go.mod h1:/KLxEy4+JdasHJLPt+tAXJJ8eXywWr/J+Q+KY+/vg7E= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/IntegrationTests/server/main.go b/IntegrationTests/server/main.go new file mode 100644 index 000000000..6e3751578 --- /dev/null +++ b/IntegrationTests/server/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "sync" + "time" + + "github.com/heroiclabs/nakama-common/runtime" +) + +// retryAttempts tracks per-test_id call counts for the retry_test RPC. +var retryAttempts sync.Map + +//goland:noinspection GoUnusedExportedFunction +func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error { + initStart := time.Now() + + if err := initializer.RegisterRpc("transform", rpcTransform); err != nil { + return err + } + + if err := initializer.RegisterRpc("always_fail", rpcAlwaysFail); err != nil { + return err + } + + if err := initializer.RegisterRpc("retry_test", rpcRetryTest); err != nil { + return err + } + + logger.Info("Test server plugin loaded in %dms", time.Since(initStart).Milliseconds()) + return nil +} + +func rpcTransform(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { + var input map[string]interface{} + if err := json.Unmarshal([]byte(payload), &input); err != nil { + return "", runtime.NewError("invalid JSON payload", 3) // INVALID_ARGUMENT + } + + msg, ok := input["message"].(string) + if !ok { + return "", runtime.NewError("missing or invalid 'message' field", 3) // INVALID_ARGUMENT + } + + // Reverse the string + runes := []rune(msg) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + + result := map[string]interface{}{ + "original": msg, + "reversed": string(runes), + "length": len([]rune(msg)), + "upper": strings.ToUpper(msg), + } + + out, err := json.Marshal(result) + if err != nil { + return "", runtime.NewError("failed to marshal response", 13) // INTERNAL + } + return string(out), nil +} + +func rpcAlwaysFail(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { + return "", runtime.NewError("always fails", 14) // UNAVAILABLE +} + +func rpcRetryTest(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { + var input struct { + TestID string `json:"test_id"` + FailCount int `json:"fail_count"` + } + if err := json.Unmarshal([]byte(payload), &input); err != nil { + return "", runtime.NewError("invalid JSON payload", 3) // INVALID_ARGUMENT + } + if input.TestID == "" { + return "", runtime.NewError("missing test_id", 3) // INVALID_ARGUMENT + } + + // Atomically increment the call count for this test_id. + var count int + if val, loaded := retryAttempts.LoadOrStore(input.TestID, new(int)); loaded { + ptr := val.(*int) + *ptr++ + count = *ptr + } else { + ptr := val.(*int) + *ptr = 1 + count = 1 + } + + if count <= input.FailCount { + return "", runtime.NewError(fmt.Sprintf("transient failure %d/%d", count, input.FailCount), 14) // UNAVAILABLE + } + + result, _ := json.Marshal(map[string]interface{}{ + "attempts": count, + }) + return string(result), nil +} diff --git a/Nakama/Config/FilterPlugin.ini b/Nakama/Config/FilterPlugin.ini deleted file mode 100644 index ccebca2f3..000000000 --- a/Nakama/Config/FilterPlugin.ini +++ /dev/null @@ -1,8 +0,0 @@ -[FilterPlugin] -; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and -; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. -; -; Examples: -; /README.txt -; /Extras/... -; /Binaries/ThirdParty/*.dll diff --git a/Nakama/Nakama.uplugin b/Nakama/Nakama.uplugin index d7b17832f..4494bb69d 100644 --- a/Nakama/Nakama.uplugin +++ b/Nakama/Nakama.uplugin @@ -16,7 +16,7 @@ "Installed": false, "Modules": [ { - "Name": "NakamaUnreal", + "Name": "NakamaApi", "Type": "Runtime", "LoadingPhase": "Default", "WhitelistPlatforms": [ @@ -28,7 +28,7 @@ ] }, { - "Name": "NakamaBlueprints", + "Name": "Nakama", "Type": "Runtime", "LoadingPhase": "Default", "WhitelistPlatforms": [ @@ -40,8 +40,8 @@ ] }, { - "Name": "NakamaTests", - "Type": "DeveloperTool", + "Name": "NakamaBlueprints", + "Type": "Runtime", "LoadingPhase": "Default", "WhitelistPlatforms": [ "Win64", diff --git a/Satori/Source/SatoriUnreal/SatoriUnreal.Build.cs b/Nakama/Source/Nakama/Nakama.Build.cs similarity index 89% rename from Satori/Source/SatoriUnreal/SatoriUnreal.Build.cs rename to Nakama/Source/Nakama/Nakama.Build.cs index 39ea75915..eeeef83b3 100644 --- a/Satori/Source/SatoriUnreal/SatoriUnreal.Build.cs +++ b/Nakama/Source/Nakama/Nakama.Build.cs @@ -17,9 +17,9 @@ using UnrealBuildTool; using System.IO; -public class SatoriUnreal : ModuleRules +public class Nakama : ModuleRules { - public SatoriUnreal(ReadOnlyTargetRules Target) : base(Target) + public Nakama(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; @@ -37,11 +37,10 @@ public SatoriUnreal(ReadOnlyTargetRules Target) : base(Target) ); - // NakamaUnreal no longer depends on NakamaCore PublicDependencyModuleNames.AddRange( new string[] { - "Core", "HTTP", "WebSockets", "JsonUtilities" + "Core", "HTTP", "WebSockets", "JsonUtilities", "NakamaApi" // ... add other public dependencies that you statically link with here ... } ); diff --git a/Nakama/Source/Nakama/Private/Nakama.cpp b/Nakama/Source/Nakama/Private/Nakama.cpp new file mode 100644 index 000000000..18c928574 --- /dev/null +++ b/Nakama/Source/Nakama/Private/Nakama.cpp @@ -0,0 +1,10113 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "Nakama.h" +#include "Containers/Ticker.h" +#include "Modules/ModuleManager.h" + +bool Nakama::IsTransientError(const FNakamaError& Error) noexcept +{ + switch (Error.Code) + { + case 0: // Connection failed (TCP refused, DNS timeout, etc.) + case 4: // DEADLINE_EXCEEDED + case 13: // INTERNAL + case 14: // UNAVAILABLE + return true; + default: + return false; + } +} + +float Nakama::CalculateBackoff(int32 Attempt, const FNakamaRetryConfig& Config) noexcept +{ + const float ExponentialDelay = FMath::Pow(2.0f, static_cast(Attempt - 1)) * Config.BaseDelayMs; + const float Jitter = FMath::FRand() * ExponentialDelay; + return Jitter / 1000.0f; +} + +namespace +{ + +/** Optionally refresh the session before calling the RPC. */ +void MaybeRefreshThenCall( + const TSharedRef& SessionState, + const FNakamaClientConfig& ClientConfig, + const FNakamaRetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)(FNakamaError(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + NakamaApi::SessionRefresh( + ClientConfig, + SessionState->RefreshToken, + {}, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const FNakamaSession& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const FNakamaError& Error) + { + (*OnError)(FNakamaError(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace +TNakamaFuture Nakama::AddFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + , Metadata + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + , Metadata + ]() + { + NakamaApi::AddFriends( + ClientConfig, + *SessionState, + Ids, + Usernames, + Metadata, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::AddFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Ids + , Usernames + , Metadata + ]() + { + NakamaApi::AddFriends( + ClientConfig, + HttpKey, + Ids, + Usernames, + Metadata, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AddGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + NakamaApi::AddGroupUsers( + ClientConfig, + *SessionState, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::AddGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , UserIds + ]() + { + NakamaApi::AddGroupUsers( + ClientConfig, + HttpKey, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::SessionRefresh( + const FNakamaClientConfig& ClientConfig, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::SessionRefresh( + ClientConfig, + Token, + Vars, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::SessionLogout( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + FString RefreshToken, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , RefreshToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , RefreshToken + ]() + { + NakamaApi::SessionLogout( + ClientConfig, + *SessionState, + Token, + RefreshToken, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::SessionLogout( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + FString RefreshToken, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , RefreshToken + ]() + { + NakamaApi::SessionLogout( + ClientConfig, + HttpKey, + Token, + RefreshToken, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateApple( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Create + , Username + ]() + { + FNakamaAccountApple Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::AuthenticateApple( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateCustom( + const FNakamaClientConfig& ClientConfig, + FString Id, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + , Create + , Username + ]() + { + FNakamaAccountCustom Account; + Account.Id = Id; + Account.Vars = Vars; + NakamaApi::AuthenticateCustom( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateDevice( + const FNakamaClientConfig& ClientConfig, + FString Id, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + , Create + , Username + ]() + { + FNakamaAccountDevice Account; + Account.Id = Id; + Account.Vars = Vars; + NakamaApi::AuthenticateDevice( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateEmail( + const FNakamaClientConfig& ClientConfig, + FString Email, + FString Password, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Email + , Password + , Vars + , Create + , Username + ]() + { + FNakamaAccountEmail Account; + Account.Email = Email; + Account.Password = Password; + Account.Vars = Vars; + NakamaApi::AuthenticateEmail( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateFacebook( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Create + , Username + , Sync + ]() + { + FNakamaAccountFacebook Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::AuthenticateFacebook( + ClientConfig, + Account, + Create, + Username, + Sync, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + FString SignedPlayerInfo, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedPlayerInfo + , Vars + , Create + , Username + ]() + { + FNakamaAccountFacebookInstantGame Account; + Account.SignedPlayerInfo = SignedPlayerInfo; + Account.Vars = Vars; + NakamaApi::AuthenticateFacebookInstantGame( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateGameCenter( + const FNakamaClientConfig& ClientConfig, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + , Create + , Username + ]() + { + FNakamaAccountGameCenter Account; + Account.PlayerId = PlayerId; + Account.BundleId = BundleId; + Account.TimestampSeconds = TimestampSeconds; + Account.Salt = Salt; + Account.Signature = Signature; + Account.PublicKeyUrl = PublicKeyUrl; + Account.Vars = Vars; + NakamaApi::AuthenticateGameCenter( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateGoogle( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Create + , Username + ]() + { + FNakamaAccountGoogle Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::AuthenticateGoogle( + ClientConfig, + Account, + Create, + Username, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::AuthenticateSteam( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Create + , Username + , Sync + ]() + { + FNakamaAccountSteam Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::AuthenticateSteam( + ClientConfig, + Account, + Create, + Username, + Sync, + [FutureState, DoRequest, OnError](const FNakamaSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::BanGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + NakamaApi::BanGroupUsers( + ClientConfig, + *SessionState, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::BanGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , UserIds + ]() + { + NakamaApi::BanGroupUsers( + ClientConfig, + HttpKey, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::BlockFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + ]() + { + NakamaApi::BlockFriends( + ClientConfig, + *SessionState, + Ids, + Usernames, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::BlockFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Ids + , Usernames + ]() + { + NakamaApi::BlockFriends( + ClientConfig, + HttpKey, + Ids, + Usernames, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::CreateGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Description + , LangTag + , AvatarUrl + , Open + , MaxCount + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Description + , LangTag + , AvatarUrl + , Open + , MaxCount + ]() + { + NakamaApi::CreateGroup( + ClientConfig, + *SessionState, + Name, + Description, + LangTag, + AvatarUrl, + Open, + MaxCount, + [FutureState, DoRequest, OnError](const FNakamaGroup& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::CreateGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Name + , Description + , LangTag + , AvatarUrl + , Open + , MaxCount + ]() + { + NakamaApi::CreateGroup( + ClientConfig, + HttpKey, + Name, + Description, + LangTag, + AvatarUrl, + Open, + MaxCount, + [FutureState, DoRequest, OnError](const FNakamaGroup& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + NakamaApi::DeleteAccount( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + ]() + { + NakamaApi::DeleteAccount( + ClientConfig, + HttpKey, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + ]() + { + NakamaApi::DeleteFriends( + ClientConfig, + *SessionState, + Ids, + Usernames, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Ids + , Usernames + ]() + { + NakamaApi::DeleteFriends( + ClientConfig, + HttpKey, + Ids, + Usernames, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + NakamaApi::DeleteGroup( + ClientConfig, + *SessionState, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + ]() + { + NakamaApi::DeleteGroup( + ClientConfig, + HttpKey, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + ]() + { + NakamaApi::DeleteLeaderboardRecord( + ClientConfig, + *SessionState, + LeaderboardId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , LeaderboardId + ]() + { + NakamaApi::DeleteLeaderboardRecord( + ClientConfig, + HttpKey, + LeaderboardId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteNotifications( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + ]() + { + NakamaApi::DeleteNotifications( + ClientConfig, + *SessionState, + Ids, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteNotifications( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Ids + ]() + { + NakamaApi::DeleteNotifications( + ClientConfig, + HttpKey, + Ids, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + ]() + { + NakamaApi::DeleteTournamentRecord( + ClientConfig, + *SessionState, + TournamentId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , TournamentId + ]() + { + NakamaApi::DeleteTournamentRecord( + ClientConfig, + HttpKey, + TournamentId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DeleteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ObjectIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ObjectIds + ]() + { + NakamaApi::DeleteStorageObjects( + ClientConfig, + *SessionState, + ObjectIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DeleteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , ObjectIds + ]() + { + NakamaApi::DeleteStorageObjects( + ClientConfig, + HttpKey, + ObjectIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::Event( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Timestamp + , External + , Properties + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Timestamp + , External + , Properties + ]() + { + NakamaApi::Event( + ClientConfig, + *SessionState, + Name, + Timestamp, + External, + Properties, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::Event( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Name + , Timestamp + , External + , Properties + ]() + { + NakamaApi::Event( + ClientConfig, + HttpKey, + Name, + Timestamp, + External, + Properties, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::GetAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaAccountResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + NakamaApi::GetAccount( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError](const FNakamaAccount& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaAccountResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::GetAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaAccountResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + ]() + { + NakamaApi::GetAccount( + ClientConfig, + HttpKey, + [FutureState, DoRequest, OnError](const FNakamaAccount& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaAccountResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::GetUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUsersResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + , FacebookIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Ids + , Usernames + , FacebookIds + ]() + { + NakamaApi::GetUsers( + ClientConfig, + *SessionState, + Ids, + Usernames, + FacebookIds, + [FutureState, DoRequest, OnError](const FNakamaUsers& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUsersResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::GetUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUsersResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Ids + , Usernames + , FacebookIds + ]() + { + NakamaApi::GetUsers( + ClientConfig, + HttpKey, + Ids, + Usernames, + FacebookIds, + [FutureState, DoRequest, OnError](const FNakamaUsers& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUsersResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::GetSubscription( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString ProductId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatedSubscriptionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ProductId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ProductId + ]() + { + NakamaApi::GetSubscription( + ClientConfig, + *SessionState, + ProductId, + [FutureState, DoRequest, OnError](const FNakamaValidatedSubscription& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatedSubscriptionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::GetSubscription( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString ProductId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatedSubscriptionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , ProductId + ]() + { + NakamaApi::GetSubscription( + ClientConfig, + HttpKey, + ProductId, + [FutureState, DoRequest, OnError](const FNakamaValidatedSubscription& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatedSubscriptionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::GetMatchmakerStats( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchmakerStatsResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + NakamaApi::GetMatchmakerStats( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError](const FNakamaMatchmakerStats& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchmakerStatsResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::GetMatchmakerStats( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchmakerStatsResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + ]() + { + NakamaApi::GetMatchmakerStats( + ClientConfig, + HttpKey, + [FutureState, DoRequest, OnError](const FNakamaMatchmakerStats& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchmakerStatsResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::Healthcheck( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + NakamaApi::Healthcheck( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::Healthcheck( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + ]() + { + NakamaApi::Healthcheck( + ClientConfig, + HttpKey, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ImportFacebookFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Reset + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Reset + ]() + { + FNakamaAccountFacebook Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::ImportFacebookFriends( + ClientConfig, + *SessionState, + Account, + Reset, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ImportFacebookFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Reset, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + , Reset + ]() + { + FNakamaAccountFacebook Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::ImportFacebookFriends( + ClientConfig, + HttpKey, + Account, + Reset, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ImportSteamFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Reset + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Reset + ]() + { + FNakamaAccountSteam Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::ImportSteamFriends( + ClientConfig, + *SessionState, + Account, + Reset, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ImportSteamFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Reset, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + , Reset + ]() + { + FNakamaAccountSteam Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::ImportSteamFriends( + ClientConfig, + HttpKey, + Account, + Reset, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::JoinGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + NakamaApi::JoinGroup( + ClientConfig, + *SessionState, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::JoinGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + ]() + { + NakamaApi::JoinGroup( + ClientConfig, + HttpKey, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::JoinTournament( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + ]() + { + NakamaApi::JoinTournament( + ClientConfig, + *SessionState, + TournamentId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::JoinTournament( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , TournamentId + ]() + { + NakamaApi::JoinTournament( + ClientConfig, + HttpKey, + TournamentId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::KickGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + NakamaApi::KickGroupUsers( + ClientConfig, + *SessionState, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::KickGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , UserIds + ]() + { + NakamaApi::KickGroupUsers( + ClientConfig, + HttpKey, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LeaveGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + ]() + { + NakamaApi::LeaveGroup( + ClientConfig, + *SessionState, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LeaveGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + ]() + { + NakamaApi::LeaveGroup( + ClientConfig, + HttpKey, + GroupId, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::LinkApple( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::LinkApple( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkCustom( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + NakamaApi::LinkCustom( + ClientConfig, + *SessionState, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkCustom( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Id + , Vars + ]() + { + NakamaApi::LinkCustom( + ClientConfig, + HttpKey, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkDevice( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + NakamaApi::LinkDevice( + ClientConfig, + *SessionState, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkDevice( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Id + , Vars + ]() + { + NakamaApi::LinkDevice( + ClientConfig, + HttpKey, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkEmail( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Email + , Password + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Email + , Password + , Vars + ]() + { + NakamaApi::LinkEmail( + ClientConfig, + *SessionState, + Email, + Password, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkEmail( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Email + , Password + , Vars + ]() + { + NakamaApi::LinkEmail( + ClientConfig, + HttpKey, + Email, + Password, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Sync + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Sync + ]() + { + FNakamaAccountFacebook Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::LinkFacebook( + ClientConfig, + *SessionState, + Account, + Sync, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + , Sync + ]() + { + FNakamaAccountFacebook Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::LinkFacebook( + ClientConfig, + HttpKey, + Account, + Sync, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedPlayerInfo + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedPlayerInfo + , Vars + ]() + { + NakamaApi::LinkFacebookInstantGame( + ClientConfig, + *SessionState, + SignedPlayerInfo, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , SignedPlayerInfo + , Vars + ]() + { + NakamaApi::LinkFacebookInstantGame( + ClientConfig, + HttpKey, + SignedPlayerInfo, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + NakamaApi::LinkGameCenter( + ClientConfig, + *SessionState, + PlayerId, + BundleId, + TimestampSeconds, + Salt, + Signature, + PublicKeyUrl, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + NakamaApi::LinkGameCenter( + ClientConfig, + HttpKey, + PlayerId, + BundleId, + TimestampSeconds, + Salt, + Signature, + PublicKeyUrl, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::LinkGoogle( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::LinkGoogle( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::LinkSteam( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Sync + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + , Sync + ]() + { + FNakamaAccountSteam Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::LinkSteam( + ClientConfig, + *SessionState, + Account, + Sync, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::LinkSteam( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Sync, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + , Sync + ]() + { + FNakamaAccountSteam Account; + Account.Token = Token; + Account.Vars = Vars; + NakamaApi::LinkSteam( + ClientConfig, + HttpKey, + Account, + Sync, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListChannelMessages( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaChannelMessageListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ChannelId + , Limit + , Forward + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ChannelId + , Limit + , Forward + , Cursor + ]() + { + NakamaApi::ListChannelMessages( + ClientConfig, + *SessionState, + ChannelId, + Limit, + Forward, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaChannelMessageList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaChannelMessageListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListChannelMessages( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaChannelMessageListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , ChannelId + , Limit + , Forward + , Cursor + ]() + { + NakamaApi::ListChannelMessages( + ClientConfig, + HttpKey, + ChannelId, + Limit, + Forward, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaChannelMessageList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaChannelMessageListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , State + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListFriends( + ClientConfig, + *SessionState, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaFriendList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListFriends( + ClientConfig, + HttpKey, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaFriendList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListFriendsOfFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendsOfFriendsListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Cursor + ]() + { + NakamaApi::ListFriendsOfFriends( + ClientConfig, + *SessionState, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaFriendsOfFriendsList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendsOfFriendsListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListFriendsOfFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendsOfFriendsListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , Cursor + ]() + { + NakamaApi::ListFriendsOfFriends( + ClientConfig, + HttpKey, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaFriendsOfFriendsList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaFriendsOfFriendsListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListGroups( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Cursor + , Limit + , LangTag + , Members + , Open + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Name + , Cursor + , Limit + , LangTag + , Members + , Open + ]() + { + NakamaApi::ListGroups( + ClientConfig, + *SessionState, + Name, + Cursor, + Limit, + LangTag, + Members, + Open, + [FutureState, DoRequest, OnError](const FNakamaGroupList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListGroups( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Name + , Cursor + , Limit + , LangTag + , Members + , Open + ]() + { + NakamaApi::ListGroups( + ClientConfig, + HttpKey, + Name, + Cursor, + Limit, + LangTag, + Members, + Open, + [FutureState, DoRequest, OnError](const FNakamaGroupList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupUserListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , Limit + , State + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListGroupUsers( + ClientConfig, + *SessionState, + GroupId, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaGroupUserList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupUserListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupUserListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListGroupUsers( + ClientConfig, + HttpKey, + GroupId, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaGroupUserList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaGroupUserListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListLeaderboardRecords( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + NakamaApi::ListLeaderboardRecords( + ClientConfig, + *SessionState, + LeaderboardId, + OwnerIds, + Limit, + Cursor, + Expiry, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListLeaderboardRecords( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , LeaderboardId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + NakamaApi::ListLeaderboardRecords( + ClientConfig, + HttpKey, + LeaderboardId, + OwnerIds, + Limit, + Cursor, + Expiry, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + NakamaApi::ListLeaderboardRecordsAroundOwner( + ClientConfig, + *SessionState, + LeaderboardId, + Limit, + OwnerId, + Expiry, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , LeaderboardId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + NakamaApi::ListLeaderboardRecordsAroundOwner( + ClientConfig, + HttpKey, + LeaderboardId, + Limit, + OwnerId, + Expiry, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListMatches( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Authoritative + , Label + , MinSize + , MaxSize + , Query + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Authoritative + , Label + , MinSize + , MaxSize + , Query + ]() + { + NakamaApi::ListMatches( + ClientConfig, + *SessionState, + Limit, + Authoritative, + Label, + MinSize, + MaxSize, + Query, + [FutureState, DoRequest, OnError](const FNakamaMatchList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListMatches( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , Authoritative + , Label + , MinSize + , MaxSize + , Query + ]() + { + NakamaApi::ListMatches( + ClientConfig, + HttpKey, + Limit, + Authoritative, + Label, + MinSize, + MaxSize, + Query, + [FutureState, DoRequest, OnError](const FNakamaMatchList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaMatchListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListParties( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaPartyListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Open + , Query + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Open + , Query + , Cursor + ]() + { + NakamaApi::ListParties( + ClientConfig, + *SessionState, + Limit, + Open, + Query, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaPartyList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaPartyListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListParties( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaPartyListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , Open + , Query + , Cursor + ]() + { + NakamaApi::ListParties( + ClientConfig, + HttpKey, + Limit, + Open, + Query, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaPartyList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaPartyListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListNotifications( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaNotificationListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , CacheableCursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , CacheableCursor + ]() + { + NakamaApi::ListNotifications( + ClientConfig, + *SessionState, + Limit, + CacheableCursor, + [FutureState, DoRequest, OnError](const FNakamaNotificationList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaNotificationListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListNotifications( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString CacheableCursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaNotificationListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , CacheableCursor + ]() + { + NakamaApi::ListNotifications( + ClientConfig, + HttpKey, + Limit, + CacheableCursor, + [FutureState, DoRequest, OnError](const FNakamaNotificationList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaNotificationListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , UserId + , Collection + , Limit + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , UserId + , Collection + , Limit + , Cursor + ]() + { + NakamaApi::ListStorageObjects( + ClientConfig, + *SessionState, + UserId, + Collection, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaStorageObjectList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , UserId + , Collection + , Limit + , Cursor + ]() + { + NakamaApi::ListStorageObjects( + ClientConfig, + HttpKey, + UserId, + Collection, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaStorageObjectList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListSubscriptions( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSubscriptionListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Cursor + ]() + { + NakamaApi::ListSubscriptions( + ClientConfig, + *SessionState, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaSubscriptionList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSubscriptionListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListSubscriptions( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSubscriptionListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Limit + , Cursor + ]() + { + NakamaApi::ListSubscriptions( + ClientConfig, + HttpKey, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaSubscriptionList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaSubscriptionListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListTournaments( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , CategoryStart + , CategoryEnd + , StartTime + , EndTime + , Limit + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , CategoryStart + , CategoryEnd + , StartTime + , EndTime + , Limit + , Cursor + ]() + { + NakamaApi::ListTournaments( + ClientConfig, + *SessionState, + CategoryStart, + CategoryEnd, + StartTime, + EndTime, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaTournamentList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListTournaments( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , CategoryStart + , CategoryEnd + , StartTime + , EndTime + , Limit + , Cursor + ]() + { + NakamaApi::ListTournaments( + ClientConfig, + HttpKey, + CategoryStart, + CategoryEnd, + StartTime, + EndTime, + Limit, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaTournamentList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListTournamentRecords( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + NakamaApi::ListTournamentRecords( + ClientConfig, + *SessionState, + TournamentId, + OwnerIds, + Limit, + Cursor, + Expiry, + [FutureState, DoRequest, OnError](const FNakamaTournamentRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListTournamentRecords( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , TournamentId + , OwnerIds + , Limit + , Cursor + , Expiry + ]() + { + NakamaApi::ListTournamentRecords( + ClientConfig, + HttpKey, + TournamentId, + OwnerIds, + Limit, + Cursor, + Expiry, + [FutureState, DoRequest, OnError](const FNakamaTournamentRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + NakamaApi::ListTournamentRecordsAroundOwner( + ClientConfig, + *SessionState, + TournamentId, + Limit, + OwnerId, + Expiry, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaTournamentRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , TournamentId + , Limit + , OwnerId + , Expiry + , Cursor + ]() + { + NakamaApi::ListTournamentRecordsAroundOwner( + ClientConfig, + HttpKey, + TournamentId, + Limit, + OwnerId, + Expiry, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaTournamentRecordList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaTournamentRecordListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ListUserGroups( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUserGroupListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , UserId + , Limit + , State + , Cursor + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , UserId + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListUserGroups( + ClientConfig, + *SessionState, + UserId, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaUserGroupList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUserGroupListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ListUserGroups( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUserGroupListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , UserId + , Limit + , State + , Cursor + ]() + { + NakamaApi::ListUserGroups( + ClientConfig, + HttpKey, + UserId, + Limit, + State, + Cursor, + [FutureState, DoRequest, OnError](const FNakamaUserGroupList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaUserGroupListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::PromoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + NakamaApi::PromoteGroupUsers( + ClientConfig, + *SessionState, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::PromoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , UserIds + ]() + { + NakamaApi::PromoteGroupUsers( + ClientConfig, + HttpKey, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::DemoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , UserIds + ]() + { + NakamaApi::DemoteGroupUsers( + ClientConfig, + *SessionState, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::DemoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , UserIds + ]() + { + NakamaApi::DemoteGroupUsers( + ClientConfig, + HttpKey, + GroupId, + UserIds, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ReadStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectsResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ObjectIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , ObjectIds + ]() + { + NakamaApi::ReadStorageObjects( + ClientConfig, + *SessionState, + ObjectIds, + [FutureState, DoRequest, OnError](const FNakamaStorageObjects& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectsResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ReadStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectsResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , ObjectIds + ]() + { + NakamaApi::ReadStorageObjects( + ClientConfig, + HttpKey, + ObjectIds, + [FutureState, DoRequest, OnError](const FNakamaStorageObjects& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectsResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::RpcFunc( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + TSharedPtr Payload, + FString HttpKey, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaRpcResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Payload + , HttpKey + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Payload + , HttpKey + ]() + { + NakamaApi::RpcFunc( + ClientConfig, + *SessionState, + Id, + Payload, + HttpKey, + [FutureState, DoRequest, OnError](const FNakamaRpc& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaRpcResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::RpcFunc( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + TSharedPtr Payload, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaRpcResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Id + , Payload + ]() + { + NakamaApi::RpcFunc( + ClientConfig, + HttpKey, + Id, + Payload, + [FutureState, DoRequest, OnError](const FNakamaRpc& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaRpcResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::UnlinkApple( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::UnlinkApple( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkCustom( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + NakamaApi::UnlinkCustom( + ClientConfig, + *SessionState, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkCustom( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Id + , Vars + ]() + { + NakamaApi::UnlinkCustom( + ClientConfig, + HttpKey, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkDevice( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Vars + ]() + { + NakamaApi::UnlinkDevice( + ClientConfig, + *SessionState, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkDevice( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Id + , Vars + ]() + { + NakamaApi::UnlinkDevice( + ClientConfig, + HttpKey, + Id, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkEmail( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Email + , Password + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Email + , Password + , Vars + ]() + { + NakamaApi::UnlinkEmail( + ClientConfig, + *SessionState, + Email, + Password, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkEmail( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Email + , Password + , Vars + ]() + { + NakamaApi::UnlinkEmail( + ClientConfig, + HttpKey, + Email, + Password, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::UnlinkFacebook( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::UnlinkFacebook( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedPlayerInfo + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedPlayerInfo + , Vars + ]() + { + NakamaApi::UnlinkFacebookInstantGame( + ClientConfig, + *SessionState, + SignedPlayerInfo, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , SignedPlayerInfo + , Vars + ]() + { + NakamaApi::UnlinkFacebookInstantGame( + ClientConfig, + HttpKey, + SignedPlayerInfo, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + NakamaApi::UnlinkGameCenter( + ClientConfig, + *SessionState, + PlayerId, + BundleId, + TimestampSeconds, + Salt, + Signature, + PublicKeyUrl, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , PlayerId + , BundleId + , TimestampSeconds + , Salt + , Signature + , PublicKeyUrl + , Vars + ]() + { + NakamaApi::UnlinkGameCenter( + ClientConfig, + HttpKey, + PlayerId, + BundleId, + TimestampSeconds, + Salt, + Signature, + PublicKeyUrl, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::UnlinkGoogle( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::UnlinkGoogle( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UnlinkSteam( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , Vars + ]() + { + NakamaApi::UnlinkSteam( + ClientConfig, + *SessionState, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UnlinkSteam( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Token + , Vars + ]() + { + NakamaApi::UnlinkSteam( + ClientConfig, + HttpKey, + Token, + Vars, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UpdateAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Username + , DisplayName + , AvatarUrl + , LangTag + , Location + , Timezone + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Username + , DisplayName + , AvatarUrl + , LangTag + , Location + , Timezone + ]() + { + NakamaApi::UpdateAccount( + ClientConfig, + *SessionState, + Username, + DisplayName, + AvatarUrl, + LangTag, + Location, + Timezone, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UpdateAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Username + , DisplayName + , AvatarUrl + , LangTag + , Location + , Timezone + ]() + { + NakamaApi::UpdateAccount( + ClientConfig, + HttpKey, + Username, + DisplayName, + AvatarUrl, + LangTag, + Location, + Timezone, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::UpdateGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , Name + , Description + , LangTag + , AvatarUrl + , Open + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , GroupId + , Name + , Description + , LangTag + , AvatarUrl + , Open + ]() + { + NakamaApi::UpdateGroup( + ClientConfig, + *SessionState, + GroupId, + Name, + Description, + LangTag, + AvatarUrl, + Open, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::UpdateGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , GroupId + , Name + , Description + , LangTag + , AvatarUrl + , Open + ]() + { + NakamaApi::UpdateGroup( + ClientConfig, + HttpKey, + GroupId, + Name, + Description, + LangTag, + AvatarUrl, + Open, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidatePurchaseApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + NakamaApi::ValidatePurchaseApple( + ClientConfig, + *SessionState, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidatePurchaseApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Receipt + , Persist + ]() + { + NakamaApi::ValidatePurchaseApple( + ClientConfig, + HttpKey, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidateSubscriptionApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + NakamaApi::ValidateSubscriptionApple( + ClientConfig, + *SessionState, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidateSubscriptionResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidateSubscriptionApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Receipt + , Persist + ]() + { + NakamaApi::ValidateSubscriptionApple( + ClientConfig, + HttpKey, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidateSubscriptionResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidatePurchaseGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Purchase, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Purchase + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Purchase + , Persist + ]() + { + NakamaApi::ValidatePurchaseGoogle( + ClientConfig, + *SessionState, + Purchase, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidatePurchaseGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Purchase, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Purchase + , Persist + ]() + { + NakamaApi::ValidatePurchaseGoogle( + ClientConfig, + HttpKey, + Purchase, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidateSubscriptionGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Receipt + , Persist + ]() + { + NakamaApi::ValidateSubscriptionGoogle( + ClientConfig, + *SessionState, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidateSubscriptionResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidateSubscriptionGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Receipt + , Persist + ]() + { + NakamaApi::ValidateSubscriptionGoogle( + ClientConfig, + HttpKey, + Receipt, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidateSubscriptionResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidateSubscriptionResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidatePurchaseHuawei( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Purchase + , Signature + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Purchase + , Signature + , Persist + ]() + { + NakamaApi::ValidatePurchaseHuawei( + ClientConfig, + *SessionState, + Purchase, + Signature, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidatePurchaseHuawei( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Purchase, + FString Signature, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Purchase + , Signature + , Persist + ]() + { + NakamaApi::ValidatePurchaseHuawei( + ClientConfig, + HttpKey, + Purchase, + Signature, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedRequest + , Persist + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , SignedRequest + , Persist + ]() + { + NakamaApi::ValidatePurchaseFacebookInstant( + ClientConfig, + *SessionState, + SignedRequest, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedRequest, + bool Persist, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , SignedRequest + , Persist + ]() + { + NakamaApi::ValidatePurchaseFacebookInstant( + ClientConfig, + HttpKey, + SignedRequest, + Persist, + [FutureState, DoRequest, OnError](const FNakamaValidatePurchaseResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaValidatePurchaseResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::WriteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , Score + , Subscore + , Metadata + , Operator + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , LeaderboardId + , Score + , Subscore + , Metadata + , Operator + ]() + { + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record; + Record.Score = Score; + Record.Subscore = Subscore; + Record.Metadata = Metadata; + Record.Operator = Operator; + NakamaApi::WriteLeaderboardRecord( + ClientConfig, + *SessionState, + LeaderboardId, + Record, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecord& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::WriteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , LeaderboardId + , Score + , Subscore + , Metadata + , Operator + ]() + { + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record; + Record.Score = Score; + Record.Subscore = Subscore; + Record.Metadata = Metadata; + Record.Operator = Operator; + NakamaApi::WriteLeaderboardRecord( + ClientConfig, + HttpKey, + LeaderboardId, + Record, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecord& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::WriteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Objects, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectAcksResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Objects + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Objects + ]() + { + NakamaApi::WriteStorageObjects( + ClientConfig, + *SessionState, + Objects, + [FutureState, DoRequest, OnError](const FNakamaStorageObjectAcks& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectAcksResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::WriteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Objects, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectAcksResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , Objects + ]() + { + NakamaApi::WriteStorageObjects( + ClientConfig, + HttpKey, + Objects, + [FutureState, DoRequest, OnError](const FNakamaStorageObjectAcks& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaStorageObjectAcksResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} +TNakamaFuture Nakama::WriteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , Score + , Subscore + , Metadata + , Operator + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , TournamentId + , Score + , Subscore + , Metadata + , Operator + ]() + { + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record; + Record.Score = Score; + Record.Subscore = Subscore; + Record.Metadata = Metadata; + Record.Operator = Operator; + NakamaApi::WriteTournamentRecord( + ClientConfig, + *SessionState, + TournamentId, + Record, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecord& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +TNakamaFuture Nakama::WriteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator, + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + , TournamentId + , Score + , Subscore + , Metadata + , Operator + ]() + { + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record; + Record.Score = Score; + Record.Subscore = Subscore; + Record.Metadata = Metadata; + Record.Operator = Operator; + NakamaApi::WriteTournamentRecord( + ClientConfig, + HttpKey, + TournamentId, + Record, + [FutureState, DoRequest, OnError](const FNakamaLeaderboardRecord& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaLeaderboardRecordResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture(FutureState); +} + +// Module implementation +class FNakamaModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogNakama, Log, TEXT("Nakama module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogNakama, Log, TEXT("Nakama module shutting down")); + } +}; + +IMPLEMENT_MODULE(FNakamaModule, Nakama) diff --git a/Nakama/Source/Nakama/Private/NakamaRt.cpp b/Nakama/Source/Nakama/Private/NakamaRt.cpp new file mode 100644 index 000000000..2cd9fa050 --- /dev/null +++ b/Nakama/Source/Nakama/Private/NakamaRt.cpp @@ -0,0 +1,1435 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRt.h" +#include "NakamaApi.h" + + + +namespace Nakama +{ +TNakamaFuture NakamaRealtimeClient::Connect( + const FNakamaWebSocketConnectionParams& Params +) noexcept +{ + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Connect(Params); +} +TNakamaFuture NakamaRealtimeClient::Channel( + const FString& Id, + const TArray& Presences, + const FNakamaRtUserPresence& Self, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelJoin( + const FString& Target, + const int32& Type, + const TOptional& Persistence, + const TOptional& Hidden +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Target.IsEmpty()) + { + Json->SetStringField(TEXT("target"), Target); + } + Json->SetNumberField(TEXT("type"), Type); + if (Persistence.IsSet()) + { + Json->SetBoolField(TEXT("persistence"), Persistence.GetValue()); + } + if (Hidden.IsSet()) + { + Json->SetBoolField(TEXT("hidden"), Hidden.GetValue()); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_join"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelLeave( + const FString& ChannelId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_leave"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelMessage( + const FString& ChannelId, + const FString& MessageId, + const TOptional& Code, + const FString& SenderId, + const FString& Username, + const FString& Content, + const FString& CreateTime, + const FString& UpdateTime, + const TOptional& Persistent, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + if (Code.IsSet()) + { + Json->SetNumberField(TEXT("code"), Code.GetValue()); + } + if (!SenderId.IsEmpty()) + { + Json->SetStringField(TEXT("sender_id"), SenderId); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (Persistent.IsSet()) + { + Json->SetBoolField(TEXT("persistent"), Persistent.GetValue()); + } + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_message"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelMessageAck( + const FString& ChannelId, + const FString& MessageId, + const TOptional& Code, + const FString& Username, + const FString& CreateTime, + const FString& UpdateTime, + const TOptional& Persistent, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + if (Code.IsSet()) + { + Json->SetNumberField(TEXT("code"), Code.GetValue()); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (Persistent.IsSet()) + { + Json->SetBoolField(TEXT("persistent"), Persistent.GetValue()); + } + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_message_ack"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelMessageSend( + const FString& ChannelId, + const FString& Content +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_message_send"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelMessageUpdate( + const FString& ChannelId, + const FString& MessageId, + const FString& Content +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_message_update"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelMessageRemove( + const FString& ChannelId, + const FString& MessageId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_message_remove"), Json); +} + +TNakamaFuture NakamaRealtimeClient::ChannelPresenceEvent( + const FString& ChannelId, + const TArray& Joins, + const TArray& Leaves, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("channel_presence_event"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Error( + const int32& Code, + const FString& Message, + const TMap& Context +) noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("code"), Code); + if (!Message.IsEmpty()) + { + Json->SetStringField(TEXT("message"), Message); + } + if (Context.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Context) + {MapObj->SetStringField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("context"), MapObj); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("error"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Match( + const FString& MatchId, + const bool& Authoritative, + const FString& Label, + const int32& Size, + const TArray& Presences, + const FNakamaRtUserPresence& Self +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetBoolField(TEXT("authoritative"), Authoritative); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetNumberField(TEXT("size"), Size); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchCreate( + const FString& Name +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_create"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchData( + const FString& MatchId, + const FNakamaRtUserPresence& Presence, + const int64& OpCode, + const TArray& Data, + const bool& Reliable +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(Data)); + Json->SetBoolField(TEXT("reliable"), Reliable); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_data"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchDataSend( + const FString& MatchId, + const int64& OpCode, + const TArray& Data, + const TArray& Presences, + const bool& Reliable +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(Data)); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetBoolField(TEXT("reliable"), Reliable); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_data_send"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchJoin( + const FString& MatchId, + const FString& Token, + const TMap& Metadata +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Metadata.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Metadata) + {MapObj->SetStringField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("metadata"), MapObj); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_join"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchLeave( + const FString& MatchId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_leave"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchPresenceEvent( + const FString& MatchId, + const TArray& Joins, + const TArray& Leaves +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("match_presence_event"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchmakerAdd( + const int32& MinCount, + const int32& MaxCount, + const FString& Query, + const TOptional& CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties +) noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("min_count"), MinCount); + Json->SetNumberField(TEXT("max_count"), MaxCount); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + if (CountMultiple.IsSet()) + { + Json->SetNumberField(TEXT("count_multiple"), CountMultiple.GetValue()); + } + if (StringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StringProperties) + {MapObj->SetStringField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (NumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : NumericProperties) + { + MapObj->SetNumberField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("matchmaker_add"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchmakerMatched( + const FString& Ticket, + const TArray& Users, + const FNakamaRtMatchmakerMatched_MatchmakerUser& Self, + const FString& MatchId, + const FString& Token +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + if (Users.Num() > 0) + { + TArray> Array; + for (const auto& Item : Users) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("users"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("matchmaker_matched"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchmakerRemove( + const FString& Ticket +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("matchmaker_remove"), Json); +} + +TNakamaFuture NakamaRealtimeClient::MatchmakerTicket( + const FString& Ticket +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("matchmaker_ticket"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Notifications( + const TArray& Notifications +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (Notifications.Num() > 0) + { + TArray> Array; + for (const auto& Item : Notifications) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("notifications"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("notifications"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Rpc( + const FString& Id, + const FString& Payload, + const FString& HttpKey +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Payload.IsEmpty()) + { + Json->SetStringField(TEXT("payload"), Payload); + } + if (!HttpKey.IsEmpty()) + { + Json->SetStringField(TEXT("http_key"), HttpKey); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("rpc"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Status( + const TArray& Presences +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("status"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StatusFollow( + const TArray& UserIds, + const TArray& Usernames +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("status_follow"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StatusPresenceEvent( + const TArray& Joins, + const TArray& Leaves +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("status_presence_event"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StatusUnfollow( + const TArray& UserIds +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("status_unfollow"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StatusUpdate( + const FString& Status +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Status.IsEmpty()) + { + Json->SetStringField(TEXT("status"), Status); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("status_update"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StreamData( + const FNakamaRtStream& Stream, + const FNakamaRtUserPresence& Sender, + const FString& Data, + const bool& Reliable +) noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), Stream.ToJson()); + Json->SetObjectField(TEXT("sender"), Sender.ToJson()); + if (!Data.IsEmpty()) + { + Json->SetStringField(TEXT("data"), Data); + } + Json->SetBoolField(TEXT("reliable"), Reliable); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("stream_data"), Json); +} + +TNakamaFuture NakamaRealtimeClient::StreamPresenceEvent( + const FNakamaRtStream& Stream, + const TArray& Joins, + const TArray& Leaves +) noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), Stream.ToJson()); + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("stream_presence_event"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Ping( + +) noexcept +{ + TSharedPtr Json = MakeShared(); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("ping"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Pong( + +) noexcept +{ + TSharedPtr Json = MakeShared(); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("pong"), Json); +} + +TNakamaFuture NakamaRealtimeClient::Party( + const FString& PartyId, + const bool& Open, + const bool& Hidden, + const int32& MaxSize, + const FNakamaRtUserPresence& Self, + const FNakamaRtUserPresence& Leader, + const TArray& Presences, + const FString& Label +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetBoolField(TEXT("hidden"), Hidden); + Json->SetNumberField(TEXT("max_size"), MaxSize); + Json->SetObjectField(TEXT("self"), Self.ToJson()); + Json->SetObjectField(TEXT("leader"), Leader.ToJson()); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyCreate( + const bool& Open, + const int32& MaxSize, + const FString& Label, + const bool& Hidden +) noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetBoolField(TEXT("open"), Open); + Json->SetNumberField(TEXT("max_size"), MaxSize); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetBoolField(TEXT("hidden"), Hidden); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_create"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyJoin( + const FString& PartyId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_join"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyLeave( + const FString& PartyId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_leave"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyPromote( + const FString& PartyId, + const FNakamaRtUserPresence& Presence +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_promote"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyLeader( + const FString& PartyId, + const FNakamaRtUserPresence& Presence +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_leader"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyAccept( + const FString& PartyId, + const FNakamaRtUserPresence& Presence +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_accept"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyRemove( + const FString& PartyId, + const FNakamaRtUserPresence& Presence +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_remove"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyClose( + const FString& PartyId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_close"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyJoinRequestList( + const FString& PartyId +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_join_request_list"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyJoinRequest( + const FString& PartyId, + const TArray& Presences +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_join_request"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyMatchmakerAdd( + const FString& PartyId, + const int32& MinCount, + const int32& MaxCount, + const FString& Query, + const TOptional& CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetNumberField(TEXT("min_count"), MinCount); + Json->SetNumberField(TEXT("max_count"), MaxCount); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + if (CountMultiple.IsSet()) + { + Json->SetNumberField(TEXT("count_multiple"), CountMultiple.GetValue()); + } + if (StringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StringProperties) + {MapObj->SetStringField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (NumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : NumericProperties) + { + MapObj->SetNumberField(Pair.Key, Pair.Value);} + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_matchmaker_add"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyMatchmakerRemove( + const FString& PartyId, + const FString& Ticket +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_matchmaker_remove"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyMatchmakerTicket( + const FString& PartyId, + const FString& Ticket +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_matchmaker_ticket"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyData( + const FString& PartyId, + const FNakamaRtUserPresence& Presence, + const int64& OpCode, + const TArray& Data +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(Data)); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_data"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyDataSend( + const FString& PartyId, + const int64& OpCode, + const TArray& Data +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(Data)); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_data_send"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyPresenceEvent( + const FString& PartyId, + const TArray& Joins, + const TArray& Leaves +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_presence_event"), Json); +} + +TNakamaFuture NakamaRealtimeClient::PartyUpdate( + const FString& PartyId, + const FString& Label, + const bool& Open, + const bool& Hidden +) noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetBoolField(TEXT("hidden"), Hidden); + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("party_update"), Json); +} + +} diff --git a/Nakama/Source/Nakama/Private/NakamaWebSocketSubsystem.cpp b/Nakama/Source/Nakama/Private/NakamaWebSocketSubsystem.cpp new file mode 100644 index 000000000..b50f46c38 --- /dev/null +++ b/Nakama/Source/Nakama/Private/NakamaWebSocketSubsystem.cpp @@ -0,0 +1,435 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NakamaWebSocketSubsystem.h" +#include "WebSocketsModule.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +DEFINE_LOG_CATEGORY(LogNakamaWebSocket); + +void UNakamaWebSocketSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + if (!FModuleManager::Get().IsModuleLoaded("WebSockets")) + { + FModuleManager::Get().LoadModule("WebSockets"); + } +} + +void UNakamaWebSocketSubsystem::Deinitialize() +{ + Close(); + + Super::Deinitialize(); +} + +void UNakamaWebSocketSubsystem::Close() +{ + StopPingLoop(); + bIsConnected = false; + + // Eagerly resolve all pending futures so their tasks don't become zombies + // that fire UE_LOG during engine teardown. + // (The async OnClosed path is skipped by the stale-socket guard when we + // call WebSocket.Reset() before the close handshake completes.) + if (ConnectionState.IsValid()) + { + auto LocalState = MoveTemp(ConnectionState); + LocalState->Resolve(FNakamaWebSocketConnectionResult{ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + { + FScopeLock Lock(&RequestsLock); + for (const auto& Request : Requests) + { + Request.Value->Resolve(FNakamaWebSocketResponse{ .ErrorCode = ENakamaWebSocketError::ConnectionClosed }); + } + Requests.Empty(); + } + + if (WebSocket.IsValid()) + { + WebSocket->Close(); + WebSocket.Reset(); + } +} + +TNakamaFuture UNakamaWebSocketSubsystem::Connect(FNakamaWebSocketConnectionParams Params) +{ + // Do we already have a connection? + if (WebSocket.IsValid()) + { + // If we have a pending connection, return failure. + if (ConnectionState.IsValid()) + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("Another WebSocket connection is in progress.")); + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionAlreadyInProgress }); + } + + // Otherwise, we have an active connection. Close it and start a new one. + UE_LOG(LogNakamaWebSocket, Warning, TEXT("Another WebSocket connection was active. Closing the old connection.")); + Close(); + } + + ConnectionParams = Params; + + // + // Construct the URL + FString Url; + if (ConnectionParams.bUseSSL) + { + Url = TEXT("wss://"); + } + else + { + Url = TEXT("ws://"); + } + + const FString TokenParam = TEXT("token=") + FGenericPlatformHttp::UrlEncode(Params.Token); + Url += ConnectionParams.Host + TEXT(":") + FString::FromInt(ConnectionParams.Port) + TEXT("/ws"); + Url += TEXT("?"); + Url += TokenParam; + + // Create the websocket + WebSocket = FWebSocketsModule::Get().CreateWebSocket(Url); + + // Use weak ptr for async safety; Pin it in the actual callback. + TWeakObjectPtr WeakThis = this; + + // + // Connectivity + WebSocket->OnConnected().AddLambda([WeakThis]() + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnConnected(); + } + }); + WebSocket->OnConnectionError().AddLambda([WeakThis, ThisSocket = WebSocket](const FString& Error) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + // Ignore stale OnConnectionError from an old socket. + if (StrongThis->WebSocket == ThisSocket) + { + StrongThis->OnConnectionError(Error); + } + } + }); + + // + // Messages + WebSocket->OnMessage().AddLambda([WeakThis](const FString& Message) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnMessage(Message); + } + }); + WebSocket->OnMessageSent().AddLambda([WeakThis](const FString& Message) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnMessageSent(Message); + } + }); + + // + // Disconnections + WebSocket->OnClosed().AddLambda([WeakThis, ThisSocket = WebSocket](int32 StatusCode, const FString& Reason, bool bWasClean) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + // Ignore stale OnClosed from an old socket. + if (StrongThis->WebSocket == ThisSocket) + { + StrongThis->OnClosed(StatusCode, Reason, bWasClean); + } + } + }); + + // + // Connect + WebSocket->Connect(); + + ConnectionState = MakeShared::FState>(); + return TNakamaFuture(ConnectionState); +} + + +void UNakamaWebSocketSubsystem::StartPingLoop() +{ + // Stop if was already running. + StopPingLoop(); + + TWeakObjectPtr WeakThis = this; + + PingTimerHandle = FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([WeakThis](float DeltaTime) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + return StrongThis->SendPing(); + } + return false; + }), + ConnectionParams.PingIntervalSeconds + ); +} +void UNakamaWebSocketSubsystem::StopPingLoop() +{ + if (PingTimerHandle.IsValid()) + { + FTSTicker::GetCoreTicker().RemoveTicker(PingTimerHandle); + PingTimerHandle.Reset(); + } +} +bool UNakamaWebSocketSubsystem::SendPing() +{ + FScopeLock Lock(&RequestsLock); + if (!bIsConnected) + { + return false; + } + + TSharedPtr Envelope = MakeShareable(new FJsonObject()); + Envelope->SetObjectField(TEXT("ping"), MakeShareable(new FJsonObject())); + + FString EnvelopeJson; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&EnvelopeJson); + FJsonSerializer::Serialize(Envelope.ToSharedRef(), Writer); + + WebSocket->Send(EnvelopeJson); + return true; +} + +TNakamaFuture UNakamaWebSocketSubsystem::Send(const FString& RequestName, const TSharedPtr& Data) +{ + // + // Create an Envelope, embed a new guid as Cid. + const FString Cid = FGuid::NewGuid().ToString(); + TSharedPtr Envelope = MakeShareable(new FJsonObject()); + + Envelope->SetStringField(TEXT("cid"), Cid); + Envelope->SetObjectField(RequestName, Data); + + FString EnvelopeJson; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&EnvelopeJson); + FJsonSerializer::Serialize(Envelope.ToSharedRef(), Writer); + + FScopeLock Lock(&RequestsLock); + + if (!bIsConnected) + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("WebSocket is not connected or invalid.")); + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::NotConnected }); + } + + // + // Create state, associate it with the Cid. + TSharedRef::FState> ResponseState = + MakeShared::FState>(); + + Requests.Add(Cid, ResponseState); + + // + // Send the envelope and return the future. + // Will activate it on message received. + WebSocket->Send(EnvelopeJson); + + return TNakamaFuture(ResponseState); +} + +void UNakamaWebSocketSubsystem::OnConnected() +{ + UE_LOG(LogNakamaWebSocket, Display, TEXT("WebSocket Connected.")); + + bIsConnected = true; + StartPingLoop(); + + TSharedPtr::FState> LocalState = MoveTemp(ConnectionState); + FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([LocalState](float) -> bool + { + LocalState->Resolve(FNakamaWebSocketConnectionResult{}); + return false; + }), 0.0f); +} + +void UNakamaWebSocketSubsystem::OnConnectionError(const FString& Error) +{ + UE_LOG(LogNakamaWebSocket, Warning, TEXT("WebSocket Connection Error: %s"), *Error); + + bIsConnected = false; + + TSharedPtr::FState> LocalState = MoveTemp(ConnectionState); + FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([LocalState](float) -> bool + { + LocalState->Resolve(FNakamaWebSocketConnectionResult{ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + return false; + }), 0.0f); +} + +void UNakamaWebSocketSubsystem::OnMessage(const FString& Message) +{ + // + // Try parse the message as JSON. + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Message); + if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) + { + if (MessageError.IsBound()) + { + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_MESSAGE_MALFORMED, TEXT("Failed to deserialize JSON.")); + } + return; + } + + // + // Log message (unless it's a pong). + const bool bIsPong = JsonObject->HasField(TEXT("pong")); + if (!bIsPong) + { + UE_LOG(LogNakamaWebSocket, Verbose, TEXT("WebSocket Message Received: %s"), *Message); + } + + // + // Try get the Cid from the message before anything else. + // Error responses from the server also carry a Cid. + FString Cid; + JsonObject->TryGetStringField(TEXT("cid"), Cid); + + // + // Handle possible error. + if (JsonObject->HasField(TEXT("error"))) + { + // If there is a pending request for this Cid, resolve it with an error + // so callers are not left waiting forever. + if (!Cid.IsEmpty()) + { + FScopeLock Lock(&RequestsLock); + if (auto* RequestPtr = Requests.Find(Cid)) + { + TSharedRef::FState> Request = *RequestPtr; + Request->Resolve(FNakamaWebSocketResponse{ .ErrorCode = ENakamaWebSocketError::ServerError }); + Requests.Remove(Cid); + } + } + + if (MessageError.IsBound()) + { + const TSharedPtr* ErrorObj; + FString ErrorMsg; + if (JsonObject->TryGetObjectField(TEXT("error"), ErrorObj)) + { + (*ErrorObj)->TryGetStringField(TEXT("message"), ErrorMsg); + } + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_MESSAGE_HASERROR, ErrorMsg); + } + return; + } + + // + // If we have a Cid, this is a response to a request. + if (!Cid.IsEmpty()) + { + FScopeLock Lock(&RequestsLock); + + // We should have a pending request for this CID. + if (auto* RequestPtr = Requests.Find(Cid)) + { + TSharedRef::FState> Request = *RequestPtr; + Request->Resolve(FNakamaWebSocketResponse{ .Data = JsonObject }); + + Requests.Remove(Cid); + } + else + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("No matching request for CID %s"), *Cid); + if (MessageError.IsBound()) + { + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_RESPONSE_NOCID, TEXT("No matching request for CID")); + } + } + + if (ServerResponseReceived.IsBound() && !bIsPong) + { + ServerResponseReceived.Broadcast(Message); + } + } + // Otherwise, this is a server event we need to handle. + else + { + if (ServerEventReceived.IsBound()) + { + ServerEventReceived.Broadcast(Message); + } + } +} + +void UNakamaWebSocketSubsystem::OnMessageSent(const FString& Message) +{ + UE_LOG(LogNakamaWebSocket, Verbose, TEXT("Message Sent: %s"), *Message); + + if (MessageSent.IsBound()) + { + MessageSent.Broadcast(Message); + } +} + +void UNakamaWebSocketSubsystem::OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean) +{ + bIsConnected = false; + StopPingLoop(); + + ELogVerbosity::Type Verbosity = bWasClean ? ELogVerbosity::Display : ELogVerbosity::Warning; + if (bWasClean) + { + UE_LOG( + LogNakamaWebSocket, + Display, + TEXT("WebSocket closed cleanly with status code: %d."), + StatusCode, + *Reason); + } + else + { + UE_LOG( + LogNakamaWebSocket, + Warning, + TEXT("Web Socket closed non-cleanly with status code: %d. Reason: %s."), + StatusCode, + *Reason); + } + + { + FScopeLock Lock(&RequestsLock); + + const ENakamaWebSocketError Code = bWasClean + ? ENakamaWebSocketError::None + : ENakamaWebSocketError::ConnectionClosed; + for (const auto& Request : Requests) + { + Request.Value->Resolve(FNakamaWebSocketResponse{ .ErrorCode = Code }); + } + Requests.Empty(); + } + + if (Closed.IsBound()) + { + Closed.Broadcast(StatusCode, Reason, bWasClean); + } +} + diff --git a/Nakama/Source/Nakama/Public/Nakama.h b/Nakama/Source/Nakama/Public/Nakama.h new file mode 100644 index 000000000..07d5424a9 --- /dev/null +++ b/Nakama/Source/Nakama/Public/Nakama.h @@ -0,0 +1,1837 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaApi.h" +#include "NakamaFuture.h" + +/** Tag type used as the value type for RPCs that return no data. */ +struct NAKAMA_API FNakamaVoid {}; + +struct NAKAMA_API FNakamaVoidResult +{ + using ValueType = FNakamaVoid; + FNakamaVoid Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaSessionResult +{ + using ValueType = FNakamaSession; + FNakamaSession Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaGroupResult +{ + using ValueType = FNakamaGroup; + FNakamaGroup Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaAccountResult +{ + using ValueType = FNakamaAccount; + FNakamaAccount Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaUsersResult +{ + using ValueType = FNakamaUsers; + FNakamaUsers Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaValidatedSubscriptionResult +{ + using ValueType = FNakamaValidatedSubscription; + FNakamaValidatedSubscription Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaMatchmakerStatsResult +{ + using ValueType = FNakamaMatchmakerStats; + FNakamaMatchmakerStats Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaChannelMessageListResult +{ + using ValueType = FNakamaChannelMessageList; + FNakamaChannelMessageList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaFriendListResult +{ + using ValueType = FNakamaFriendList; + FNakamaFriendList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaFriendsOfFriendsListResult +{ + using ValueType = FNakamaFriendsOfFriendsList; + FNakamaFriendsOfFriendsList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaGroupListResult +{ + using ValueType = FNakamaGroupList; + FNakamaGroupList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaGroupUserListResult +{ + using ValueType = FNakamaGroupUserList; + FNakamaGroupUserList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaLeaderboardRecordListResult +{ + using ValueType = FNakamaLeaderboardRecordList; + FNakamaLeaderboardRecordList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaMatchListResult +{ + using ValueType = FNakamaMatchList; + FNakamaMatchList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaPartyListResult +{ + using ValueType = FNakamaPartyList; + FNakamaPartyList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaNotificationListResult +{ + using ValueType = FNakamaNotificationList; + FNakamaNotificationList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaStorageObjectListResult +{ + using ValueType = FNakamaStorageObjectList; + FNakamaStorageObjectList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaSubscriptionListResult +{ + using ValueType = FNakamaSubscriptionList; + FNakamaSubscriptionList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaTournamentListResult +{ + using ValueType = FNakamaTournamentList; + FNakamaTournamentList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaTournamentRecordListResult +{ + using ValueType = FNakamaTournamentRecordList; + FNakamaTournamentRecordList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaUserGroupListResult +{ + using ValueType = FNakamaUserGroupList; + FNakamaUserGroupList Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaStorageObjectsResult +{ + using ValueType = FNakamaStorageObjects; + FNakamaStorageObjects Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaRpcResult +{ + using ValueType = FNakamaRpc; + FNakamaRpc Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaValidatePurchaseResponseResult +{ + using ValueType = FNakamaValidatePurchaseResponse; + FNakamaValidatePurchaseResponse Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaValidateSubscriptionResponseResult +{ + using ValueType = FNakamaValidateSubscriptionResponse; + FNakamaValidateSubscriptionResponse Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaLeaderboardRecordResult +{ + using ValueType = FNakamaLeaderboardRecord; + FNakamaLeaderboardRecord Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +struct NAKAMA_API FNakamaStorageObjectAcksResult +{ + using ValueType = FNakamaStorageObjectAcks; + FNakamaStorageObjectAcks Value{}; + FNakamaError Error; + bool bIsError = true; +}; + +/** Retry configuration for transient error handling + session auto-refresh. */ +struct NAKAMA_API FNakamaRetryConfig +{ + /** Maximum number of retry attempts (0 = no retries). */ + int32 MaxRetries = 4; + + /** Base delay in milliseconds before first retry. Actual delay uses exponential backoff with full jitter. */ + int32 BaseDelayMs = 500; + + /** Whether to automatically refresh expired sessions before making API calls. */ + bool bAutoRefreshSession = true; + + /** How many seconds before expiry a session is considered "about to expire" and gets refreshed. */ + int64 AutoRefreshBufferSeconds = 300; + + /** HTTP request timeout in seconds. */ + float Timeout = 10.0f; + + /** + * Called after a session is automatically refreshed, so callers can persist the updated session. + * + * If the callback captures a UObject pointer, set OnSessionRefreshedOwner to that object. + * The callback will be skipped if the owner has been garbage-collected, preventing + * dangling-pointer crashes during in-flight retries. + */ + TFunction OnSessionRefreshed; + + /** Optional weak owner for OnSessionRefreshed. When set, the callback is skipped if the owner is stale. */ + TWeakObjectPtr OnSessionRefreshedOwner; +}; + +/** + * High-level Nakama API: free functions with retry logic + session auto-refresh. + * + * Transient errors (UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED) are + * automatically retried with exponential backoff and full jitter. + */ +namespace Nakama +{ + /** Returns true if the error code is considered transient (eligible for retry). */ + NAKAMA_API bool IsTransientError(const FNakamaError& Error) noexcept; + + /** Compute backoff delay in seconds for a given attempt using exponential backoff with full jitter. */ + NAKAMA_API float CalculateBackoff(int32 Attempt, const FNakamaRetryConfig& Config) noexcept; + + /** Add friends by ID or username to a user's account. */ + NAKAMA_API TNakamaFuture AddFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add friends by ID or username to a user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture AddFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add users to a group. */ + NAKAMA_API TNakamaFuture AddGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add users to a group. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture AddGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */ + NAKAMA_API TNakamaFuture SessionRefresh( + const FNakamaClientConfig& ClientConfig, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ + NAKAMA_API TNakamaFuture SessionLogout( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + FString RefreshToken, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture SessionLogout( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + FString RefreshToken, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with an Apple ID against the server. */ + NAKAMA_API TNakamaFuture AuthenticateApple( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a custom id against the server. */ + NAKAMA_API TNakamaFuture AuthenticateCustom( + const FNakamaClientConfig& ClientConfig, + FString Id, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a device id against the server. */ + NAKAMA_API TNakamaFuture AuthenticateDevice( + const FNakamaClientConfig& ClientConfig, + FString Id, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with an email+password against the server. */ + NAKAMA_API TNakamaFuture AuthenticateEmail( + const FNakamaClientConfig& ClientConfig, + FString Email, + FString Password, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a Facebook OAuth token against the server. */ + NAKAMA_API TNakamaFuture AuthenticateFacebook( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a Facebook Instant Game token against the server. */ + NAKAMA_API TNakamaFuture AuthenticateFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + FString SignedPlayerInfo, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Apple's GameCenter against the server. */ + NAKAMA_API TNakamaFuture AuthenticateGameCenter( + const FNakamaClientConfig& ClientConfig, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Google against the server. */ + NAKAMA_API TNakamaFuture AuthenticateGoogle( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Steam against the server. */ + NAKAMA_API TNakamaFuture AuthenticateSteam( + const FNakamaClientConfig& ClientConfig, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Ban a set of users from a group. */ + NAKAMA_API TNakamaFuture BanGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Ban a set of users from a group. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture BanGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Block one or more users by ID or username. */ + NAKAMA_API TNakamaFuture BlockFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Block one or more users by ID or username. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture BlockFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Create a new group with the current user as the owner. */ + NAKAMA_API TNakamaFuture CreateGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Create a new group with the current user as the owner. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture CreateGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the current user's account. */ + NAKAMA_API TNakamaFuture DeleteAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more users by ID or username. */ + NAKAMA_API TNakamaFuture DeleteFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more users by ID or username. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a group by ID. */ + NAKAMA_API TNakamaFuture DeleteGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a group by ID. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a leaderboard record. */ + NAKAMA_API TNakamaFuture DeleteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a leaderboard record. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more notifications for the current user. */ + NAKAMA_API TNakamaFuture DeleteNotifications( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more notifications for the current user. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteNotifications( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a tournament record. */ + NAKAMA_API TNakamaFuture DeleteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a tournament record. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more objects by ID or username. */ + NAKAMA_API TNakamaFuture DeleteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more objects by ID or username. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DeleteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Submit an event for processing in the server's registered runtime custom events handler. */ + NAKAMA_API TNakamaFuture Event( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Submit an event for processing in the server's registered runtime custom events handler. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture Event( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch the current user's account. */ + NAKAMA_API TNakamaFuture GetAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture GetAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch zero or more users by ID and/or username. */ + NAKAMA_API TNakamaFuture GetUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch zero or more users by ID and/or username. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture GetUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get subscription by product id. */ + NAKAMA_API TNakamaFuture GetSubscription( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString ProductId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get subscription by product id. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture GetSubscription( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString ProductId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get matchmaker stats. */ + NAKAMA_API TNakamaFuture GetMatchmakerStats( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get matchmaker stats. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture GetMatchmakerStats( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. */ + NAKAMA_API TNakamaFuture Healthcheck( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture Healthcheck( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Facebook friends and add them to a user's account. */ + NAKAMA_API TNakamaFuture ImportFacebookFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Facebook friends and add them to a user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ImportFacebookFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Reset, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Steam friends and add them to a user's account. */ + NAKAMA_API TNakamaFuture ImportSteamFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Steam friends and add them to a user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ImportSteamFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Reset, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Immediately join an open group, or request to join a closed one. */ + NAKAMA_API TNakamaFuture JoinGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Immediately join an open group, or request to join a closed one. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture JoinGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Attempt to join an open and running tournament. */ + NAKAMA_API TNakamaFuture JoinTournament( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Attempt to join an open and running tournament. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture JoinTournament( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Kick a set of users from a group. */ + NAKAMA_API TNakamaFuture KickGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Kick a set of users from a group. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture KickGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Leave a group the user is a member of. */ + NAKAMA_API TNakamaFuture LeaveGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Leave a group the user is a member of. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LeaveGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an Apple ID to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an Apple ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a custom ID to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkCustom( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a custom ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkCustom( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a device ID to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkDevice( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a device ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkDevice( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an email+password to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkEmail( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an email+password to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkEmail( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook Instant Game to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook Instant Game to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Apple's GameCenter to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Apple's GameCenter to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Google to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Google to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Steam to the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture LinkSteam( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Steam to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture LinkSteam( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + bool Sync, + const TMap& Vars = {}, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List a channel's message history. */ + NAKAMA_API TNakamaFuture ListChannelMessages( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List a channel's message history. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListChannelMessages( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all friends for the current user. */ + NAKAMA_API TNakamaFuture ListFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all friends for the current user. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List friends of friends for the current user. */ + NAKAMA_API TNakamaFuture ListFriendsOfFriends( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List friends of friends for the current user. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListFriendsOfFriends( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups based on given filters. */ + NAKAMA_API TNakamaFuture ListGroups( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups based on given filters. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListGroups( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all users that are part of a group. */ + NAKAMA_API TNakamaFuture ListGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all users that are part of a group. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records. */ + NAKAMA_API TNakamaFuture ListLeaderboardRecords( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListLeaderboardRecords( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records around the target ownerId. */ + NAKAMA_API TNakamaFuture ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records around the target ownerId. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List running matches and optionally filter by matching criteria. */ + NAKAMA_API TNakamaFuture ListMatches( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List running matches and optionally filter by matching criteria. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListMatches( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List parties and optionally filter by matching criteria. */ + NAKAMA_API TNakamaFuture ListParties( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List parties and optionally filter by matching criteria. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListParties( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch list of notifications. */ + NAKAMA_API TNakamaFuture ListNotifications( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch list of notifications. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListNotifications( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString CacheableCursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List publicly readable storage objects in a given collection. */ + NAKAMA_API TNakamaFuture ListStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List publicly readable storage objects in a given collection. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List user's subscriptions. */ + NAKAMA_API TNakamaFuture ListSubscriptions( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List user's subscriptions. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListSubscriptions( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List current or upcoming tournaments. */ + NAKAMA_API TNakamaFuture ListTournaments( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List current or upcoming tournaments. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListTournaments( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records. */ + NAKAMA_API TNakamaFuture ListTournamentRecords( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListTournamentRecords( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records for a given owner. */ + NAKAMA_API TNakamaFuture ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records for a given owner. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups the current user belongs to. */ + NAKAMA_API TNakamaFuture ListUserGroups( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups the current user belongs to. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ListUserGroups( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Promote a set of users in a group to the next role up. */ + NAKAMA_API TNakamaFuture PromoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Promote a set of users in a group to the next role up. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture PromoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Demote a set of users in a group to the next role down. */ + NAKAMA_API TNakamaFuture DemoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Demote a set of users in a group to the next role down. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture DemoteGroupUsers( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get storage objects. */ + NAKAMA_API TNakamaFuture ReadStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get storage objects. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ReadStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& ObjectIds, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Execute a Lua function on the server. */ + NAKAMA_API TNakamaFuture RpcFunc( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + TSharedPtr Payload, + FString HttpKey, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Execute a Lua function on the server. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture RpcFunc( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + TSharedPtr Payload, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the Apple ID from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the Apple ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the custom ID from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkCustom( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the custom ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkCustom( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the device ID from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkDevice( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the device ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkDevice( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Id, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the email+password from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkEmail( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the email+password from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkEmail( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkFacebook( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook Instant Game profile from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook Instant Game profile from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkFacebookInstantGame( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Apple's GameCenter from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Apple's GameCenter from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkGameCenter( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Google from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Google from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Steam from the social profiles on the current user's account. */ + NAKAMA_API TNakamaFuture UnlinkSteam( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Steam from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UnlinkSteam( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Token, + const TMap& Vars, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in the current user's account. */ + NAKAMA_API TNakamaFuture UpdateAccount( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in the current user's account. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UpdateAccount( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in a given group. */ + NAKAMA_API TNakamaFuture UpdateGroup( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in a given group. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture UpdateGroup( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple IAP Receipt */ + NAKAMA_API TNakamaFuture ValidatePurchaseApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple IAP Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidatePurchaseApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple Subscription Receipt */ + NAKAMA_API TNakamaFuture ValidateSubscriptionApple( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple Subscription Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidateSubscriptionApple( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google IAP Receipt */ + NAKAMA_API TNakamaFuture ValidatePurchaseGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Purchase, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google IAP Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidatePurchaseGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Purchase, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google Subscription Receipt */ + NAKAMA_API TNakamaFuture ValidateSubscriptionGoogle( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google Subscription Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidateSubscriptionGoogle( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Receipt, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Huawei IAP Receipt */ + NAKAMA_API TNakamaFuture ValidatePurchaseHuawei( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Huawei IAP Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidatePurchaseHuawei( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString Purchase, + FString Signature, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate FB Instant IAP Receipt */ + NAKAMA_API TNakamaFuture ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate FB Instant IAP Receipt (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString SignedRequest, + bool Persist, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a leaderboard. */ + NAKAMA_API TNakamaFuture WriteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString LeaderboardId, + int64 Score = 0, + int64 Subscore = 0, + FString Metadata = TEXT(""), + ENakamaOperator Operator = static_cast(0), + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a leaderboard. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture WriteLeaderboardRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString LeaderboardId, + int64 Score = 0, + int64 Subscore = 0, + FString Metadata = TEXT(""), + ENakamaOperator Operator = static_cast(0), + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write objects into the storage engine. */ + NAKAMA_API TNakamaFuture WriteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + const TArray& Objects, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write objects into the storage engine. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture WriteStorageObjects( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + const TArray& Objects, + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a tournament. */ + NAKAMA_API TNakamaFuture WriteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FNakamaSession& Session, + FString TournamentId, + int64 Score = 0, + int64 Subscore = 0, + FString Metadata = TEXT(""), + ENakamaOperator Operator = static_cast(0), + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a tournament. (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture WriteTournamentRecord( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + FString TournamentId, + int64 Score = 0, + int64 Subscore = 0, + FString Metadata = TEXT(""), + ENakamaOperator Operator = static_cast(0), + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +} diff --git a/Nakama/Source/Nakama/Public/NakamaFuture.h b/Nakama/Source/Nakama/Public/NakamaFuture.h new file mode 100644 index 000000000..cc373b253 --- /dev/null +++ b/Nakama/Source/Nakama/Public/NakamaFuture.h @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "AsyncFuture.h" + +/** + * Nakama-flavoured alias for TAsyncFuture. + * All three .Next() overloads are inherited from TAsyncFuture and dispatch + * user callbacks to the game thread. + */ +template +using TNakamaFuture = TAsyncFuture; + +/** Type trait for TNakamaFuture (delegates to TIsTAsyncFuture). */ +template using TIsTNakamaFuture = TIsTAsyncFuture; + +/** + * Create a pre-resolved TNakamaFuture. + * Thin wrapper around MakeCompletedAsyncFuture for backward compatibility. + */ +template +TNakamaFuture MakeCompletedFuture(ResultT Value) +{ + return MakeCompletedAsyncFuture(MoveTemp(Value)); +} diff --git a/Nakama/Source/Nakama/Public/NakamaRt.h b/Nakama/Source/Nakama/Public/NakamaRt.h new file mode 100644 index 000000000..06d0c2a8b --- /dev/null +++ b/Nakama/Source/Nakama/Public/NakamaRt.h @@ -0,0 +1,729 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaWebSocketSubsystem.h" +#include "NakamaRtTypes.h" + +namespace Nakama +{ + class NakamaRealtimeClient + { + private: + TWeakObjectPtr WebSocketSubsystem; + + public: + explicit NakamaRealtimeClient(UGameInstance* InGi) + { + if (InGi == nullptr) + { + UE_LOG(LogNakama, Error, TEXT("NakamaRealtimeClient constructor received null GameInstance pointer.")); + return; + } + WebSocketSubsystem = InGi->GetSubsystem(); + } + ~NakamaRealtimeClient() + { + if (WebSocketSubsystem.IsValid()) + { + WebSocketSubsystem->Close(); + } + } + + NakamaRealtimeClient(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient& operator=(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient(NakamaRealtimeClient&&) = delete; + NakamaRealtimeClient& operator=(NakamaRealtimeClient&&) = delete; + + /** Connect (or reconnect) the WebSocket. Returns a future that resolves once the + * handshake completes or fails. */ + NAKAMA_API TNakamaFuture Connect( + const FNakamaWebSocketConnectionParams& Params + ) noexcept; + + /** + * A response from a channel join operation. + * + * @param Id The ID of the channel. + * @param Presences The users currently in the channel. + * @param Self A reference to the current user's presence in the channel. + * @param RoomName The name of the chat room, or an empty string if this message was not sent through a chat room. + * @param GroupId The ID of the group, or an empty string if this message was not sent through a group channel. + * @param UserIdOne The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + * @param UserIdTwo The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + */ + NAKAMA_API TNakamaFuture Channel( + const FString& Id, + const TArray& Presences, + const FNakamaRtUserPresence& Self, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo + ) noexcept; + + /** + * Join a realtime chat channel. + * + * @param Target The user ID to DM with, group ID to chat with, or room channel name to join. + * @param Type The type of the chat channel. + * @param Persistence Whether messages sent on this channel should be persistent. + * @param Hidden Whether the user should appear in the channel's presence list and events. + */ + NAKAMA_API TNakamaFuture ChannelJoin( + const FString& Target, + const int32& Type, + const TOptional& Persistence, + const TOptional& Hidden + ) noexcept; + + /** + * Leave a realtime chat channel. + * + * @param ChannelId The ID of the channel to leave. + */ + NAKAMA_API TNakamaFuture ChannelLeave( + const FString& ChannelId + ) noexcept; + + /** + * An incoming message on a realtime chat channel. + * + * @param ChannelId The channel this message belongs to. + * @param MessageId The unique ID of this message. + * @param Code The code representing a message type or category. + * @param SenderId Message sender, usually a user ID. + * @param Username The username of the message sender, if any. + * @param Content The content payload. + * @param CreateTime The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + * @param UpdateTime The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + * @param Persistent True if the message was persisted to the channel's history, false otherwise. + * @param RoomName The name of the chat room, or an empty string if this message was not sent through a chat room. + * @param GroupId The ID of the group, or an empty string if this message was not sent through a group channel. + * @param UserIdOne The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + * @param UserIdTwo The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + */ + NAKAMA_API TNakamaFuture ChannelMessage( + const FString& ChannelId, + const FString& MessageId, + const TOptional& Code, + const FString& SenderId, + const FString& Username, + const FString& Content, + const FString& CreateTime, + const FString& UpdateTime, + const TOptional& Persistent, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo + ) noexcept; + + /** + * An acknowledgement received in response to sending a message on a chat channel. + * + * @param ChannelId The channel the message was sent to. + * @param MessageId The unique ID assigned to the message. + * @param Code The code representing a message type or category. + * @param Username Username of the message sender. + * @param CreateTime The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + * @param UpdateTime The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + * @param Persistent True if the message was persisted to the channel's history, false otherwise. + * @param RoomName The name of the chat room, or an empty string if this message was not sent through a chat room. + * @param GroupId The ID of the group, or an empty string if this message was not sent through a group channel. + * @param UserIdOne The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + * @param UserIdTwo The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + */ + NAKAMA_API TNakamaFuture ChannelMessageAck( + const FString& ChannelId, + const FString& MessageId, + const TOptional& Code, + const FString& Username, + const FString& CreateTime, + const FString& UpdateTime, + const TOptional& Persistent, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo + ) noexcept; + + /** + * Send a message to a realtime chat channel. + * + * @param ChannelId The channel to sent to. + * @param Content Message content. + */ + NAKAMA_API TNakamaFuture ChannelMessageSend( + const FString& ChannelId, + const FString& Content + ) noexcept; + + /** + * Update a message previously sent to a realtime chat channel. + * + * @param ChannelId The channel the message was sent to. + * @param MessageId The ID assigned to the message to update. + * @param Content New message content. + */ + NAKAMA_API TNakamaFuture ChannelMessageUpdate( + const FString& ChannelId, + const FString& MessageId, + const FString& Content + ) noexcept; + + /** + * Remove a message previously sent to a realtime chat channel. + * + * @param ChannelId The channel the message was sent to. + * @param MessageId The ID assigned to the message to update. + */ + NAKAMA_API TNakamaFuture ChannelMessageRemove( + const FString& ChannelId, + const FString& MessageId + ) noexcept; + + /** + * Presence update for a particular realtime chat channel. + * + * @param ChannelId The channel identifier this event is for. + * @param Joins Presences joining the channel as part of this event, if any. + * @param Leaves Presences leaving the channel as part of this event, if any. + * @param RoomName The name of the chat room, or an empty string if this message was not sent through a chat room. + * @param GroupId The ID of the group, or an empty string if this message was not sent through a group channel. + * @param UserIdOne The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + * @param UserIdTwo The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + */ + NAKAMA_API TNakamaFuture ChannelPresenceEvent( + const FString& ChannelId, + const TArray& Joins, + const TArray& Leaves, + const FString& RoomName, + const FString& GroupId, + const FString& UserIdOne, + const FString& UserIdTwo + ) noexcept; + + /** + * Describes an error which occurred on the server. + * + * @param Code The error code which should be one of "Error.Code" enums. + * @param Message A message in English to help developers debug the response. + * @param Context Additional error details which may be different for each response. + */ + NAKAMA_API TNakamaFuture Error( + const int32& Code, + const FString& Message, + const TMap& Context + ) noexcept; + + /** + * Incoming information about a realtime match. + * + * @param MatchId The match unique ID. + * @param Authoritative True if it's an server-managed authoritative match, false otherwise. + * @param Label Match label, if any. + * @param Size The number of users currently in the match. + * @param Presences The users currently in the match. + * @param Self A reference to the current user's presence in the match. + */ + NAKAMA_API TNakamaFuture Match( + const FString& MatchId, + const bool& Authoritative, + const FString& Label, + const int32& Size, + const TArray& Presences, + const FNakamaRtUserPresence& Self + ) noexcept; + + /** + * A client to server request to create a realtime match. + * + * @param Name Optional name to use when creating the match. + */ + NAKAMA_API TNakamaFuture MatchCreate( + const FString& Name + ) noexcept; + + /** + * Incoming realtime match data delivered from the server. + * + * @param MatchId The match unique ID. + * @param Presence A reference to the user presence that sent this data, if any. + * @param OpCode Op code value. + * @param Data Data payload, if any. + * @param Reliable True if this data was delivered reliably, false otherwise. + */ + NAKAMA_API TNakamaFuture MatchData( + const FString& MatchId, + const FNakamaRtUserPresence& Presence, + const int64& OpCode, + const TArray& Data, + const bool& Reliable + ) noexcept; + + /** + * A client to server request to send data to a realtime match. + * + * @param MatchId The match unique ID. + * @param OpCode Op code value. + * @param Data Data payload, if any. + * @param Presences List of presences in the match to deliver to, if filtering is required. Otherwise deliver to everyone in the match. + * @param Reliable True if the data should be sent reliably, false otherwise. + */ + NAKAMA_API TNakamaFuture MatchDataSend( + const FString& MatchId, + const int64& OpCode, + const TArray& Data, + const TArray& Presences, + const bool& Reliable + ) noexcept; + + /** + * A client to server request to join a realtime match. + * + * @param Metadata An optional set of key-value metadata pairs to be passed to the match handler, if any. + */ + NAKAMA_API TNakamaFuture MatchJoin( + const FString& MatchId, + const FString& Token, + const TMap& Metadata + ) noexcept; + + /** + * A client to server request to leave a realtime match. + * + * @param MatchId The match unique ID. + */ + NAKAMA_API TNakamaFuture MatchLeave( + const FString& MatchId + ) noexcept; + + /** + * Presence update for a particular realtime match. + * + * @param MatchId The match unique ID. + * @param Joins User presences that have just joined the match. + * @param Leaves User presences that have just left the match. + */ + NAKAMA_API TNakamaFuture MatchPresenceEvent( + const FString& MatchId, + const TArray& Joins, + const TArray& Leaves + ) noexcept; + + /** + * Submit a new matchmaking process request. + * + * @param MinCount Minimum total user count to match together. + * @param MaxCount Maximum total user count to match together. + * @param Query Filter query used to identify suitable users. + * @param CountMultiple Optional multiple of the count that must be satisfied. + * @param StringProperties String properties. + * @param NumericProperties Numeric properties. + */ + NAKAMA_API TNakamaFuture MatchmakerAdd( + const int32& MinCount, + const int32& MaxCount, + const FString& Query, + const TOptional& CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties + ) noexcept; + + /** + * A successful matchmaking result. + * + * @param Ticket The matchmaking ticket that has completed. + * @param Users The users that have been matched together, and information about their matchmaking data. + * @param Self A reference to the current user and their properties. + */ + NAKAMA_API TNakamaFuture MatchmakerMatched( + const FString& Ticket, + const TArray& Users, + const FNakamaRtMatchmakerMatched_MatchmakerUser& Self, + const FString& MatchId, + const FString& Token + ) noexcept; + + /** + * Cancel a matchmaking process using a ticket. + * + * @param Ticket The ticket to cancel. + */ + NAKAMA_API TNakamaFuture MatchmakerRemove( + const FString& Ticket + ) noexcept; + + /** + * A response from starting a new matchmaking process. + * + * @param Ticket The ticket that can be used to cancel matchmaking. + */ + NAKAMA_API TNakamaFuture MatchmakerTicket( + const FString& Ticket + ) noexcept; + + /** + * Notifications send by the server. + * + * @param Notifications Collection of notifications. + */ + NAKAMA_API TNakamaFuture Notifications( + const TArray& Notifications + ) noexcept; + + /** + * RPC call or response. + * + * @param Id The identifier of the function. + * @param Payload The payload of the function which must be a JSON object. + * @param HttpKey The authentication key used when executed as a non-client HTTP request. + */ + NAKAMA_API TNakamaFuture Rpc( + const FString& Id, + const FString& Payload, + const FString& HttpKey + ) noexcept; + + /** + * An incoming status snapshot for some set of users. + * + * @param Presences User statuses. + */ + NAKAMA_API TNakamaFuture Status( + const TArray& Presences + ) noexcept; + + /** + * Start following some set of users to receive their status updates. + * + * @param UserIds User IDs to follow. + * @param Usernames Usernames to follow. + */ + NAKAMA_API TNakamaFuture StatusFollow( + const TArray& UserIds, + const TArray& Usernames + ) noexcept; + + /** + * An incoming status update. + * + * @param Joins New statuses for the user. + * @param Leaves Previous statuses for the user. + */ + NAKAMA_API TNakamaFuture StatusPresenceEvent( + const TArray& Joins, + const TArray& Leaves + ) noexcept; + + /** + * Stop following some set of users to no longer receive their status updates. + * + * @param UserIds Users to unfollow. + */ + NAKAMA_API TNakamaFuture StatusUnfollow( + const TArray& UserIds + ) noexcept; + + /** + * Set the user's own status. + * + * @param Status Status string to set, if not present the user will appear offline. + */ + NAKAMA_API TNakamaFuture StatusUpdate( + const FString& Status + ) noexcept; + + /** + * A data message delivered over a stream. + * + * @param Stream The stream this data message relates to. + * @param Sender The sender, if any. + * @param Data Arbitrary contents of the data message. + * @param Reliable True if this data was delivered reliably, false otherwise. + */ + NAKAMA_API TNakamaFuture StreamData( + const FNakamaRtStream& Stream, + const FNakamaRtUserPresence& Sender, + const FString& Data, + const bool& Reliable + ) noexcept; + + /** + * Presence update for a particular stream. + * + * @param Stream The stream this event relates to. + * @param Joins Presences joining the stream as part of this event, if any. + * @param Leaves Presences leaving the stream as part of this event, if any. + */ + NAKAMA_API TNakamaFuture StreamPresenceEvent( + const FNakamaRtStream& Stream, + const TArray& Joins, + const TArray& Leaves + ) noexcept; + + /** + * Application-level heartbeat and connection check. + * + */ + NAKAMA_API TNakamaFuture Ping( + + ) noexcept; + + /** + * Application-level heartbeat and connection check response. + * + */ + NAKAMA_API TNakamaFuture Pong( + + ) noexcept; + + /** + * Incoming information about a party. + * + * @param PartyId Unique party identifier. + * @param Open Open flag. + * @param Hidden Hidden flag. + * @param MaxSize Maximum number of party members. + * @param Self Self. + * @param Leader Leader. + * @param Presences All current party members. + * @param Label Label for party listing. + */ + NAKAMA_API TNakamaFuture Party( + const FString& PartyId, + const bool& Open, + const bool& Hidden, + const int32& MaxSize, + const FNakamaRtUserPresence& Self, + const FNakamaRtUserPresence& Leader, + const TArray& Presences, + const FString& Label + ) noexcept; + + /** + * Create a party. + * + * @param Open Whether or not the party will require join requests to be approved by the party leader. + * @param MaxSize Maximum number of party members. + * @param Label Label + * @param Hidden Whether the party is visible in party listings. + */ + NAKAMA_API TNakamaFuture PartyCreate( + const bool& Open, + const int32& MaxSize, + const FString& Label, + const bool& Hidden + ) noexcept; + + /** + * Join a party, or request to join if the party is not open. + * + * @param PartyId Party ID to join. + */ + NAKAMA_API TNakamaFuture PartyJoin( + const FString& PartyId + ) noexcept; + + /** + * Leave a party. + * + * @param PartyId Party ID to leave. + */ + NAKAMA_API TNakamaFuture PartyLeave( + const FString& PartyId + ) noexcept; + + /** + * Promote a new party leader. + * + * @param PartyId Party ID to promote a new leader for. + * @param Presence The presence of an existing party member to promote as the new leader. + */ + NAKAMA_API TNakamaFuture PartyPromote( + const FString& PartyId, + const FNakamaRtUserPresence& Presence + ) noexcept; + + /** + * Announcement of a new party leader. + * + * @param PartyId Party ID to announce the new leader for. + * @param Presence The presence of the new party leader. + */ + NAKAMA_API TNakamaFuture PartyLeader( + const FString& PartyId, + const FNakamaRtUserPresence& Presence + ) noexcept; + + /** + * Accept a request to join. + * + * @param PartyId Party ID to accept a join request for. + * @param Presence The presence to accept as a party member. + */ + NAKAMA_API TNakamaFuture PartyAccept( + const FString& PartyId, + const FNakamaRtUserPresence& Presence + ) noexcept; + + /** + * Kick a party member, or decline a request to join. + * + * @param PartyId Party ID to remove/reject from. + * @param Presence The presence to remove or reject. + */ + NAKAMA_API TNakamaFuture PartyRemove( + const FString& PartyId, + const FNakamaRtUserPresence& Presence + ) noexcept; + + /** + * End a party, kicking all party members and closing it. + * + * @param PartyId Party ID to close. + */ + NAKAMA_API TNakamaFuture PartyClose( + const FString& PartyId + ) noexcept; + + /** + * Request a list of pending join requests for a party. + * + * @param PartyId Party ID to get a list of join requests for. + */ + NAKAMA_API TNakamaFuture PartyJoinRequestList( + const FString& PartyId + ) noexcept; + + /** + * Incoming notification for one or more new presences attempting to join the party. + * + * @param PartyId Party ID these presences are attempting to join. + * @param Presences Presences attempting to join. + */ + NAKAMA_API TNakamaFuture PartyJoinRequest( + const FString& PartyId, + const TArray& Presences + ) noexcept; + + /** + * Begin matchmaking as a party. + * + * @param PartyId Party ID. + * @param MinCount Minimum total user count to match together. + * @param MaxCount Maximum total user count to match together. + * @param Query Filter query used to identify suitable users. + * @param CountMultiple Optional multiple of the count that must be satisfied. + * @param StringProperties String properties. + * @param NumericProperties Numeric properties. + */ + NAKAMA_API TNakamaFuture PartyMatchmakerAdd( + const FString& PartyId, + const int32& MinCount, + const int32& MaxCount, + const FString& Query, + const TOptional& CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties + ) noexcept; + + /** + * Cancel a party matchmaking process using a ticket. + * + * @param PartyId Party ID. + * @param Ticket The ticket to cancel. + */ + NAKAMA_API TNakamaFuture PartyMatchmakerRemove( + const FString& PartyId, + const FString& Ticket + ) noexcept; + + /** + * A response from starting a new party matchmaking process. + * + * @param PartyId Party ID. + * @param Ticket The ticket that can be used to cancel matchmaking. + */ + NAKAMA_API TNakamaFuture PartyMatchmakerTicket( + const FString& PartyId, + const FString& Ticket + ) noexcept; + + /** + * Incoming party data delivered from the server. + * + * @param PartyId The party ID. + * @param Presence A reference to the user presence that sent this data, if any. + * @param OpCode Op code value. + * @param Data Data payload, if any. + */ + NAKAMA_API TNakamaFuture PartyData( + const FString& PartyId, + const FNakamaRtUserPresence& Presence, + const int64& OpCode, + const TArray& Data + ) noexcept; + + /** + * A client to server request to send data to a party. + * + * @param PartyId Party ID to send to. + * @param OpCode Op code value. + * @param Data Data payload, if any. + */ + NAKAMA_API TNakamaFuture PartyDataSend( + const FString& PartyId, + const int64& OpCode, + const TArray& Data + ) noexcept; + + /** + * Presence update for a particular party. + * + * @param PartyId The party ID. + * @param Joins User presences that have just joined the party. + * @param Leaves User presences that have just left the party. + */ + NAKAMA_API TNakamaFuture PartyPresenceEvent( + const FString& PartyId, + const TArray& Joins, + const TArray& Leaves + ) noexcept; + + /** + * Update Party label and whether it's open or closed. + * + * @param PartyId Party ID. + * @param Label Label to set. + * @param Open Change the party to open or closed. + * @param Hidden Whether the party is visible in party listings. + */ + NAKAMA_API TNakamaFuture PartyUpdate( + const FString& PartyId, + const FString& Label, + const bool& Open, + const bool& Hidden + ) noexcept; + + }; +} + diff --git a/Nakama/Source/Nakama/Public/NakamaWebSocketSubsystem.h b/Nakama/Source/Nakama/Public/NakamaWebSocketSubsystem.h new file mode 100644 index 000000000..c15298af0 --- /dev/null +++ b/Nakama/Source/Nakama/Public/NakamaWebSocketSubsystem.h @@ -0,0 +1,132 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "IWebSocket.h" +#include "NakamaFuture.h" +#include +#include "NakamaWebSocketSubsystem.generated.h" + +NAKAMA_API DECLARE_LOG_CATEGORY_EXTERN(LogNakamaWebSocket, Log, All); + +enum class ENakamaWebSocketError : uint8 +{ + None = 0, + ConnectionAlreadyInProgress = 1, + ConnectionFailed = 2, + NotConnected = 3, + ConnectionClosed = 4, + ServerError = 5, +}; + +/** Result of a realtime send operation. Check ErrorCode before accessing Data — + * Data is nullptr when ErrorCode != ENakamaWebSocketError::None. */ +struct FNakamaWebSocketResponse +{ + ENakamaWebSocketError ErrorCode = ENakamaWebSocketError::None; + TSharedPtr Data; +}; +struct FNakamaWebSocketConnectionResult +{ + ENakamaWebSocketError ErrorCode = ENakamaWebSocketError::None; +}; +struct FNakamaWebSocketConnectionParams +{ + FString Host; + int32 Port; + FString Token; + float PingIntervalSeconds = 2.0f; + bool bUseSSL; +}; + +enum class EWebSocketMessageError : uint8 +{ + WS_ERROR_NONE = 0, + WS_ERROR_MESSAGE_MALFORMED = 1, + WS_ERROR_MESSAGE_HASERROR = 2, + WS_ERROR_RESPONSE_NOCID = 3, +}; + +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateServerResponseReceived, const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateServerEventReceived, const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateMessageSent, const FString&); +DECLARE_MULTICAST_DELEGATE_TwoParams(FDelegateMessageError, EWebSocketMessageError, const FString&); +DECLARE_MULTICAST_DELEGATE_ThreeParams(FDelegateClosed, int32, const FString&, bool); + +UCLASS() +class NAKAMA_API UNakamaWebSocketSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + // + // Implement the subsystem + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + // + // Public delegates + FDelegateServerResponseReceived ServerResponseReceived; + FDelegateServerEventReceived ServerEventReceived; + FDelegateMessageSent MessageSent; + FDelegateMessageError MessageError; + FDelegateClosed Closed; + + TNakamaFuture Connect(FNakamaWebSocketConnectionParams Params); + + TNakamaFuture Send(const FString& RequestName, const TSharedPtr& Data); + + void Close(); + + int32 GetPendingRequestCount() + { + FScopeLock Lock(&RequestsLock); + return Requests.Num(); + } + +private: + + TSharedPtr WebSocket; + + TSharedPtr::FState> ConnectionState; + std::atomic bIsConnected{false}; + + // Current ongoing requests + TMap::FState>> Requests; + FCriticalSection RequestsLock; + + // Params for current connection + FNakamaWebSocketConnectionParams ConnectionParams; + + FTSTicker::FDelegateHandle PingTimerHandle; + + // + // Ping-ponging + void StartPingLoop(); + void StopPingLoop(); + bool SendPing(); + + // + // WebSocket Callbacks + void OnConnected(); + void OnConnectionError(const FString& Error); + void OnMessage(const FString& Message); + void OnMessageSent(const FString& Message); + void OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean); +}; + diff --git a/Nakama/Source/NakamaUnreal/NakamaUnreal.Build.cs b/Nakama/Source/NakamaApi/NakamaApi.Build.cs similarity index 89% rename from Nakama/Source/NakamaUnreal/NakamaUnreal.Build.cs rename to Nakama/Source/NakamaApi/NakamaApi.Build.cs index 46b969a93..862b2ef4d 100644 --- a/Nakama/Source/NakamaUnreal/NakamaUnreal.Build.cs +++ b/Nakama/Source/NakamaApi/NakamaApi.Build.cs @@ -17,9 +17,9 @@ using UnrealBuildTool; using System.IO; -public class NakamaUnreal : ModuleRules +public class NakamaApi : ModuleRules { - public NakamaUnreal(ReadOnlyTargetRules Target) : base(Target) + public NakamaApi(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; @@ -37,11 +37,10 @@ public NakamaUnreal(ReadOnlyTargetRules Target) : base(Target) ); - // NakamaUnreal no longer depends on NakamaCore PublicDependencyModuleNames.AddRange( new string[] { - "Core", "HTTP", "WebSockets", "JsonUtilities" + "Core", "HTTP", "JsonUtilities" // ... add other public dependencies that you statically link with here ... } ); diff --git a/Nakama/Source/NakamaApi/Private/NakamaApi.cpp b/Nakama/Source/NakamaApi/Private/NakamaApi.cpp new file mode 100644 index 000000000..45f2967c3 --- /dev/null +++ b/Nakama/Source/NakamaApi/Private/NakamaApi.cpp @@ -0,0 +1,6332 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaApi.h" +#include "NakamaHttpHelper.h" + +#include "Modules/ModuleManager.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +DEFINE_LOG_CATEGORY(LogNakama); + +using namespace NakamaHttpInternal; + +void NakamaApi::AddFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (!Metadata.IsEmpty()) + { + QueryParams.Add(TEXT("metadata=") + FGenericPlatformHttp::UrlEncode(Metadata)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AddFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (!Metadata.IsEmpty()) + { + QueryParams.Add(TEXT("metadata=") + FGenericPlatformHttp::UrlEncode(Metadata)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AddGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/add"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AddGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/add"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::SessionRefresh( + const FNakamaClientConfig& Config, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/session/refresh"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::SessionLogout( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/session/logout"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Body->SetStringField(TEXT("refresh_token"), RefreshToken); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::SessionLogout( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/session/logout"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Body->SetStringField(TEXT("refresh_token"), RefreshToken); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateApple( + const FNakamaClientConfig& Config, + FNakamaAccountApple Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/apple"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateCustom( + const FNakamaClientConfig& Config, + FNakamaAccountCustom Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/custom"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateDevice( + const FNakamaClientConfig& Config, + FNakamaAccountDevice Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/device"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateEmail( + const FNakamaClientConfig& Config, + FNakamaAccountEmail Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/email"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateFacebook( + const FNakamaClientConfig& Config, + FNakamaAccountFacebook Account, + bool Create, + FString Username, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/facebook"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + QueryParams.Add(FString::Printf(TEXT("sync=%s"), Sync ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateFacebookInstantGame( + const FNakamaClientConfig& Config, + FNakamaAccountFacebookInstantGame Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/facebookinstantgame"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateGameCenter( + const FNakamaClientConfig& Config, + FNakamaAccountGameCenter Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/gamecenter"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateGoogle( + const FNakamaClientConfig& Config, + FNakamaAccountGoogle Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/google"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::AuthenticateSteam( + const FNakamaClientConfig& Config, + FNakamaAccountSteam Account, + bool Create, + FString Username, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/authenticate/steam"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("create=%s"), Create ? TEXT("true") : TEXT("false"))); + if (!Username.IsEmpty()) + { + QueryParams.Add(TEXT("username=") + FGenericPlatformHttp::UrlEncode(Username)); + } + QueryParams.Add(FString::Printf(TEXT("sync=%s"), Sync ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Basic;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FNakamaSession Result = FNakamaSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::BanGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/ban"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::BanGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/ban"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::BlockFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/block"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::BlockFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/block"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::CreateGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Body->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Body->SetBoolField(TEXT("open"), Open); + Body->SetNumberField(TEXT("max_count"), MaxCount); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroup Result = FNakamaGroup::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::CreateGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Body->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Body->SetBoolField(TEXT("open"), Open); + Body->SetNumberField(TEXT("max_count"), MaxCount); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroup Result = FNakamaGroup::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account"); + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account");TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteNotifications( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/notification"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteNotifications( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/notification"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteTournamentRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteTournamentRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage/delete"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("object_ids"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DeleteStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage/delete"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("object_ids"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::Event( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/event"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + Body->SetStringField(TEXT("timestamp"), Timestamp); + Body->SetBoolField(TEXT("external"), External); + if (Properties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Properties) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("properties"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::Event( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/event"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + Body->SetStringField(TEXT("timestamp"), Timestamp); + Body->SetBoolField(TEXT("external"), External); + if (Properties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Properties) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("properties"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account"); + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaAccount Result = FNakamaAccount::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account");TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaAccount Result = FNakamaAccount::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/user"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : FacebookIds) + { + QueryParams.Add(TEXT("facebook_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaUsers Result = FNakamaUsers::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/user"); + TArray QueryParams; + for (const FString& Item : Ids) + { + QueryParams.Add(TEXT("ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Usernames) + { + QueryParams.Add(TEXT("usernames=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : FacebookIds) + { + QueryParams.Add(TEXT("facebook_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaUsers Result = FNakamaUsers::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetSubscription( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString ProductId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/{product_id}"); + { const FString Encoded_ProductId = FGenericPlatformHttp::UrlEncode(ProductId); Endpoint = Endpoint.Replace(TEXT("{product_id}"), *Encoded_ProductId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatedSubscription Result = FNakamaValidatedSubscription::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetSubscription( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString ProductId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/{product_id}"); + { const FString Encoded_ProductId = FGenericPlatformHttp::UrlEncode(ProductId); Endpoint = Endpoint.Replace(TEXT("{product_id}"), *Encoded_ProductId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatedSubscription Result = FNakamaValidatedSubscription::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetMatchmakerStats( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/matchmaker/stats"); + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaMatchmakerStats Result = FNakamaMatchmakerStats::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::GetMatchmakerStats( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/matchmaker/stats");TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaMatchmakerStats Result = FNakamaMatchmakerStats::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::Healthcheck( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/healthcheck"); + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::Healthcheck( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/healthcheck");TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ImportFacebookFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountFacebook Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/facebook"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("reset=%s"), Reset ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ImportFacebookFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountFacebook Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/facebook"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("reset=%s"), Reset ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ImportSteamFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountSteam Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/steam"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("reset=%s"), Reset ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ImportSteamFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountSteam Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/steam"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("reset=%s"), Reset ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::JoinGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/join"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::JoinGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/join"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::JoinTournament( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}/join"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::JoinTournament( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}/join"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::KickGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/kick"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::KickGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/kick"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LeaveGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/leave"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LeaveGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/leave"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkCustom( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/custom"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkCustom( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/custom"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkDevice( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/device"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkDevice( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/device"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkEmail( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/email"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Email.IsEmpty()) + { + Body->SetStringField(TEXT("email"), Email); + } + if (!Password.IsEmpty()) + { + Body->SetStringField(TEXT("password"), Password); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkEmail( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/email"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Email.IsEmpty()) + { + Body->SetStringField(TEXT("email"), Email); + } + if (!Password.IsEmpty()) + { + Body->SetStringField(TEXT("password"), Password); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkFacebook( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountFacebook Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/facebook"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("sync=%s"), Sync ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkFacebook( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountFacebook Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/facebook"); + TArray QueryParams; + QueryParams.Add(FString::Printf(TEXT("sync=%s"), Sync ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = Account.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/facebookinstantgame"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!SignedPlayerInfo.IsEmpty()) + { + Body->SetStringField(TEXT("signed_player_info"), SignedPlayerInfo); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/facebookinstantgame"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!SignedPlayerInfo.IsEmpty()) + { + Body->SetStringField(TEXT("signed_player_info"), SignedPlayerInfo); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkGameCenter( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/gamecenter"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!PlayerId.IsEmpty()) + { + Body->SetStringField(TEXT("player_id"), PlayerId); + } + if (!BundleId.IsEmpty()) + { + Body->SetStringField(TEXT("bundle_id"), BundleId); + } + Body->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); + if (!Salt.IsEmpty()) + { + Body->SetStringField(TEXT("salt"), Salt); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + if (!PublicKeyUrl.IsEmpty()) + { + Body->SetStringField(TEXT("public_key_url"), PublicKeyUrl); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkGameCenter( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/gamecenter"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!PlayerId.IsEmpty()) + { + Body->SetStringField(TEXT("player_id"), PlayerId); + } + if (!BundleId.IsEmpty()) + { + Body->SetStringField(TEXT("bundle_id"), BundleId); + } + Body->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); + if (!Salt.IsEmpty()) + { + Body->SetStringField(TEXT("salt"), Salt); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + if (!PublicKeyUrl.IsEmpty()) + { + Body->SetStringField(TEXT("public_key_url"), PublicKeyUrl); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkSteam( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountSteam Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/steam"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + Body->SetObjectField(TEXT("account"), Account.ToJson()); + Body->SetBoolField(TEXT("sync"), Sync); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::LinkSteam( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountSteam Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/link/steam"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + Body->SetObjectField(TEXT("account"), Account.ToJson()); + Body->SetBoolField(TEXT("sync"), Sync); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListChannelMessages( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/channel/{channel_id}"); + { const FString Encoded_ChannelId = FGenericPlatformHttp::UrlEncode(ChannelId); Endpoint = Endpoint.Replace(TEXT("{channel_id}"), *Encoded_ChannelId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("forward=%s"), Forward ? TEXT("true") : TEXT("false"))); + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaChannelMessageList Result = FNakamaChannelMessageList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListChannelMessages( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/channel/{channel_id}"); + { const FString Encoded_ChannelId = FGenericPlatformHttp::UrlEncode(ChannelId); Endpoint = Endpoint.Replace(TEXT("{channel_id}"), *Encoded_ChannelId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("forward=%s"), Forward ? TEXT("true") : TEXT("false"))); + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaChannelMessageList Result = FNakamaChannelMessageList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaFriendList Result = FNakamaFriendList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaFriendList Result = FNakamaFriendList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListFriendsOfFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/friends"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaFriendsOfFriendsList Result = FNakamaFriendsOfFriendsList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListFriendsOfFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/friend/friends"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaFriendsOfFriendsList Result = FNakamaFriendsOfFriendsList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListGroups( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group"); + TArray QueryParams; + if (!Name.IsEmpty()) + { + QueryParams.Add(TEXT("name=") + FGenericPlatformHttp::UrlEncode(Name)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!LangTag.IsEmpty()) + { + QueryParams.Add(TEXT("lang_tag=") + FGenericPlatformHttp::UrlEncode(LangTag)); + } + if (Members != 0) + { + QueryParams.Add(FString::Printf(TEXT("members=%d"), Members)); + } + QueryParams.Add(FString::Printf(TEXT("open=%s"), Open ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroupList Result = FNakamaGroupList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListGroups( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group"); + TArray QueryParams; + if (!Name.IsEmpty()) + { + QueryParams.Add(TEXT("name=") + FGenericPlatformHttp::UrlEncode(Name)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!LangTag.IsEmpty()) + { + QueryParams.Add(TEXT("lang_tag=") + FGenericPlatformHttp::UrlEncode(LangTag)); + } + if (Members != 0) + { + QueryParams.Add(FString::Printf(TEXT("members=%d"), Members)); + } + QueryParams.Add(FString::Printf(TEXT("open=%s"), Open ? TEXT("true") : TEXT("false"))); + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroupList Result = FNakamaGroupList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/user"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroupUserList Result = FNakamaGroupUserList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/user"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaGroupUserList Result = FNakamaGroupUserList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListLeaderboardRecords( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + for (const FString& Item : OwnerIds) + { + QueryParams.Add(TEXT("owner_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecordList Result = FNakamaLeaderboardRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListLeaderboardRecords( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + for (const FString& Item : OwnerIds) + { + QueryParams.Add(TEXT("owner_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecordList Result = FNakamaLeaderboardRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}/owner/{owner_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + { const FString Encoded_OwnerId = FGenericPlatformHttp::UrlEncode(OwnerId); Endpoint = Endpoint.Replace(TEXT("{owner_id}"), *Encoded_OwnerId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecordList Result = FNakamaLeaderboardRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}/owner/{owner_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + { const FString Encoded_OwnerId = FGenericPlatformHttp::UrlEncode(OwnerId); Endpoint = Endpoint.Replace(TEXT("{owner_id}"), *Encoded_OwnerId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecordList Result = FNakamaLeaderboardRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListMatches( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/match"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("authoritative=%s"), Authoritative ? TEXT("true") : TEXT("false"))); + if (!Label.IsEmpty()) + { + QueryParams.Add(TEXT("label=") + FGenericPlatformHttp::UrlEncode(Label)); + } + if (MinSize != 0) + { + QueryParams.Add(FString::Printf(TEXT("min_size=%d"), MinSize)); + } + if (MaxSize != 0) + { + QueryParams.Add(FString::Printf(TEXT("max_size=%d"), MaxSize)); + } + if (!Query.IsEmpty()) + { + QueryParams.Add(TEXT("query=") + FGenericPlatformHttp::UrlEncode(Query)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaMatchList Result = FNakamaMatchList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListMatches( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/match"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("authoritative=%s"), Authoritative ? TEXT("true") : TEXT("false"))); + if (!Label.IsEmpty()) + { + QueryParams.Add(TEXT("label=") + FGenericPlatformHttp::UrlEncode(Label)); + } + if (MinSize != 0) + { + QueryParams.Add(FString::Printf(TEXT("min_size=%d"), MinSize)); + } + if (MaxSize != 0) + { + QueryParams.Add(FString::Printf(TEXT("max_size=%d"), MaxSize)); + } + if (!Query.IsEmpty()) + { + QueryParams.Add(TEXT("query=") + FGenericPlatformHttp::UrlEncode(Query)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaMatchList Result = FNakamaMatchList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListParties( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/party"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("open=%s"), Open ? TEXT("true") : TEXT("false"))); + if (!Query.IsEmpty()) + { + QueryParams.Add(TEXT("query=") + FGenericPlatformHttp::UrlEncode(Query)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaPartyList Result = FNakamaPartyList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListParties( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/party"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("open=%s"), Open ? TEXT("true") : TEXT("false"))); + if (!Query.IsEmpty()) + { + QueryParams.Add(TEXT("query=") + FGenericPlatformHttp::UrlEncode(Query)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaPartyList Result = FNakamaPartyList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListNotifications( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/notification"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!CacheableCursor.IsEmpty()) + { + QueryParams.Add(TEXT("cacheable_cursor=") + FGenericPlatformHttp::UrlEncode(CacheableCursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaNotificationList Result = FNakamaNotificationList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListNotifications( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString CacheableCursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/notification"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!CacheableCursor.IsEmpty()) + { + QueryParams.Add(TEXT("cacheable_cursor=") + FGenericPlatformHttp::UrlEncode(CacheableCursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaNotificationList Result = FNakamaNotificationList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage/{collection}"); + { const FString Encoded_Collection = FGenericPlatformHttp::UrlEncode(Collection); Endpoint = Endpoint.Replace(TEXT("{collection}"), *Encoded_Collection); } + TArray QueryParams; + if (!UserId.IsEmpty()) + { + QueryParams.Add(TEXT("user_id=") + FGenericPlatformHttp::UrlEncode(UserId)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjectList Result = FNakamaStorageObjectList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage/{collection}"); + { const FString Encoded_Collection = FGenericPlatformHttp::UrlEncode(Collection); Endpoint = Endpoint.Replace(TEXT("{collection}"), *Encoded_Collection); } + TArray QueryParams; + if (!UserId.IsEmpty()) + { + QueryParams.Add(TEXT("user_id=") + FGenericPlatformHttp::UrlEncode(UserId)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjectList Result = FNakamaStorageObjectList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListSubscriptions( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + Body->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Body->SetStringField(TEXT("cursor"), Cursor); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaSubscriptionList Result = FNakamaSubscriptionList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListSubscriptions( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + Body->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Body->SetStringField(TEXT("cursor"), Cursor); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaSubscriptionList Result = FNakamaSubscriptionList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournaments( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament"); + TArray QueryParams; + if (CategoryStart != 0) + { + QueryParams.Add(FString::Printf(TEXT("category_start=%d"), CategoryStart)); + } + if (CategoryEnd != 0) + { + QueryParams.Add(FString::Printf(TEXT("category_end=%d"), CategoryEnd)); + } + if (StartTime != 0) + { + QueryParams.Add(FString::Printf(TEXT("start_time=%d"), StartTime)); + } + if (EndTime != 0) + { + QueryParams.Add(FString::Printf(TEXT("end_time=%d"), EndTime)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentList Result = FNakamaTournamentList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournaments( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament"); + TArray QueryParams; + if (CategoryStart != 0) + { + QueryParams.Add(FString::Printf(TEXT("category_start=%d"), CategoryStart)); + } + if (CategoryEnd != 0) + { + QueryParams.Add(FString::Printf(TEXT("category_end=%d"), CategoryEnd)); + } + if (StartTime != 0) + { + QueryParams.Add(FString::Printf(TEXT("start_time=%d"), StartTime)); + } + if (EndTime != 0) + { + QueryParams.Add(FString::Printf(TEXT("end_time=%d"), EndTime)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentList Result = FNakamaTournamentList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournamentRecords( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + for (const FString& Item : OwnerIds) + { + QueryParams.Add(TEXT("owner_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentRecordList Result = FNakamaTournamentRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournamentRecords( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + for (const FString& Item : OwnerIds) + { + QueryParams.Add(TEXT("owner_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentRecordList Result = FNakamaTournamentRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}/owner/{owner_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + { const FString Encoded_OwnerId = FGenericPlatformHttp::UrlEncode(OwnerId); Endpoint = Endpoint.Replace(TEXT("{owner_id}"), *Encoded_OwnerId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentRecordList Result = FNakamaTournamentRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}/owner/{owner_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + { const FString Encoded_OwnerId = FGenericPlatformHttp::UrlEncode(OwnerId); Endpoint = Endpoint.Replace(TEXT("{owner_id}"), *Encoded_OwnerId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (Expiry != 0) + { + QueryParams.Add(FString::Printf(TEXT("expiry=%lld"), Expiry)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaTournamentRecordList Result = FNakamaTournamentRecordList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListUserGroups( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/user/{user_id}/group"); + { const FString Encoded_UserId = FGenericPlatformHttp::UrlEncode(UserId); Endpoint = Endpoint.Replace(TEXT("{user_id}"), *Encoded_UserId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaUserGroupList Result = FNakamaUserGroupList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ListUserGroups( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/user/{user_id}/group"); + { const FString Encoded_UserId = FGenericPlatformHttp::UrlEncode(UserId); Endpoint = Endpoint.Replace(TEXT("{user_id}"), *Encoded_UserId); } + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + if (State != 0) + { + QueryParams.Add(FString::Printf(TEXT("state=%d"), State)); + } + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaUserGroupList Result = FNakamaUserGroupList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::PromoteGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/promote"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::PromoteGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/promote"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DemoteGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/demote"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::DemoteGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}/demote"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + for (const FString& Item : UserIds) + { + QueryParams.Add(TEXT("user_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ReadStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("object_ids"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjects Result = FNakamaStorageObjects::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ReadStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("object_ids"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjects Result = FNakamaStorageObjects::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::RpcFunc( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + TSharedPtr Payload, + FString HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/rpc/{id}"); + { const FString Encoded_Id = FGenericPlatformHttp::UrlEncode(Id); Endpoint = Endpoint.Replace(TEXT("{id}"), *Encoded_Id); } + TArray QueryParams; + if (!HttpKey.IsEmpty()) + { + QueryParams.Add(TEXT("http_key=") + FGenericPlatformHttp::UrlEncode(HttpKey)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer; + + FString BodyString; + if (Payload.IsValid()) + { + BodyString = SerializeJsonEscaped(Payload); + } + + SendRequest(Config, Endpoint, TEXT("POST"), BodyString, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaRpc Result = FNakamaRpc::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::RpcFunc( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + TSharedPtr Payload, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/rpc/{id}"); + { const FString Encoded_Id = FGenericPlatformHttp::UrlEncode(Id); Endpoint = Endpoint.Replace(TEXT("{id}"), *Encoded_Id); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + FString BodyString; + if (Payload.IsValid()) + { + BodyString = SerializeJsonEscaped(Payload); + } + + SendRequest(Config, Endpoint, TEXT("POST"), BodyString, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaRpc Result = FNakamaRpc::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkCustom( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/custom"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkCustom( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/custom"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkDevice( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/device"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkDevice( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/device"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkEmail( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/email"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Email.IsEmpty()) + { + Body->SetStringField(TEXT("email"), Email); + } + if (!Password.IsEmpty()) + { + Body->SetStringField(TEXT("password"), Password); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkEmail( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/email"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Email.IsEmpty()) + { + Body->SetStringField(TEXT("email"), Email); + } + if (!Password.IsEmpty()) + { + Body->SetStringField(TEXT("password"), Password); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkFacebook( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/facebook"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkFacebook( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/facebook"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/facebookinstantgame"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!SignedPlayerInfo.IsEmpty()) + { + Body->SetStringField(TEXT("signed_player_info"), SignedPlayerInfo); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/facebookinstantgame"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!SignedPlayerInfo.IsEmpty()) + { + Body->SetStringField(TEXT("signed_player_info"), SignedPlayerInfo); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkGameCenter( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/gamecenter"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!PlayerId.IsEmpty()) + { + Body->SetStringField(TEXT("player_id"), PlayerId); + } + if (!BundleId.IsEmpty()) + { + Body->SetStringField(TEXT("bundle_id"), BundleId); + } + Body->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); + if (!Salt.IsEmpty()) + { + Body->SetStringField(TEXT("salt"), Salt); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + if (!PublicKeyUrl.IsEmpty()) + { + Body->SetStringField(TEXT("public_key_url"), PublicKeyUrl); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkGameCenter( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/gamecenter"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!PlayerId.IsEmpty()) + { + Body->SetStringField(TEXT("player_id"), PlayerId); + } + if (!BundleId.IsEmpty()) + { + Body->SetStringField(TEXT("bundle_id"), BundleId); + } + Body->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); + if (!Salt.IsEmpty()) + { + Body->SetStringField(TEXT("salt"), Salt); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + if (!PublicKeyUrl.IsEmpty()) + { + Body->SetStringField(TEXT("public_key_url"), PublicKeyUrl); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkSteam( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/steam"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UnlinkSteam( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account/unlink/steam"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("vars"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UpdateAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Username.IsEmpty()) + { + Body->SetStringField(TEXT("username"), Username); + } + if (!DisplayName.IsEmpty()) + { + Body->SetStringField(TEXT("display_name"), DisplayName); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!Location.IsEmpty()) + { + Body->SetStringField(TEXT("location"), Location); + } + if (!Timezone.IsEmpty()) + { + Body->SetStringField(TEXT("timezone"), Timezone); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UpdateAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/account"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Username.IsEmpty()) + { + Body->SetStringField(TEXT("username"), Username); + } + if (!DisplayName.IsEmpty()) + { + Body->SetStringField(TEXT("display_name"), DisplayName); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!Location.IsEmpty()) + { + Body->SetStringField(TEXT("location"), Location); + } + if (!Timezone.IsEmpty()) + { + Body->SetStringField(TEXT("timezone"), Timezone); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UpdateGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!GroupId.IsEmpty()) + { + Body->SetStringField(TEXT("group_id"), GroupId); + } + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Body->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Body->SetBoolField(TEXT("open"), Open); + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::UpdateGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/group/{group_id}"); + { const FString Encoded_GroupId = FGenericPlatformHttp::UrlEncode(GroupId); Endpoint = Endpoint.Replace(TEXT("{group_id}"), *Encoded_GroupId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!GroupId.IsEmpty()) + { + Body->SetStringField(TEXT("group_id"), GroupId); + } + if (!Name.IsEmpty()) + { + Body->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Body->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Body->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Body->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Body->SetBoolField(TEXT("open"), Open); + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidateSubscriptionApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidateSubscriptionResponse Result = FNakamaValidateSubscriptionResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidateSubscriptionApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/apple"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidateSubscriptionResponse Result = FNakamaValidateSubscriptionResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Purchase, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Purchase.IsEmpty()) + { + Body->SetStringField(TEXT("purchase"), Purchase); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Purchase, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Purchase.IsEmpty()) + { + Body->SetStringField(TEXT("purchase"), Purchase); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidateSubscriptionGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidateSubscriptionResponse Result = FNakamaValidateSubscriptionResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidateSubscriptionGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/subscription/google"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Receipt.IsEmpty()) + { + Body->SetStringField(TEXT("receipt"), Receipt); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidateSubscriptionResponse Result = FNakamaValidateSubscriptionResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseHuawei( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/huawei"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Purchase.IsEmpty()) + { + Body->SetStringField(TEXT("purchase"), Purchase); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseHuawei( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Purchase, + FString Signature, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/huawei"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!Purchase.IsEmpty()) + { + Body->SetStringField(TEXT("purchase"), Purchase); + } + if (!Signature.IsEmpty()) + { + Body->SetStringField(TEXT("signature"), Signature); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/facebookinstant"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!SignedRequest.IsEmpty()) + { + Body->SetStringField(TEXT("signed_request"), SignedRequest); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedRequest, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/iap/purchase/facebookinstant"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (!SignedRequest.IsEmpty()) + { + Body->SetStringField(TEXT("signed_request"), SignedRequest); + } + Body->SetBoolField(TEXT("persist"), Persist); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaValidatePurchaseResponse Result = FNakamaValidatePurchaseResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = Record.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecord Result = FNakamaLeaderboardRecord::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/leaderboard/{leaderboard_id}"); + { const FString Encoded_LeaderboardId = FGenericPlatformHttp::UrlEncode(LeaderboardId); Endpoint = Endpoint.Replace(TEXT("{leaderboard_id}"), *Encoded_LeaderboardId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = Record.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecord Result = FNakamaLeaderboardRecord::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Objects, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (Objects.Num() > 0) + { + TArray> Array; + for (const auto& Item : Objects) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("objects"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjectAcks Result = FNakamaStorageObjectAcks::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Objects, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/storage"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = MakeShared(); + if (Objects.Num() > 0) + { + TArray> Array; + for (const auto& Item : Objects) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("objects"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaStorageObjectAcks Result = FNakamaStorageObjectAcks::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteTournamentRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ENakamaRequestAuth AuthType = ENakamaRequestAuth::Bearer;TSharedPtr Body; + Body = Record.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecord Result = FNakamaLeaderboardRecord::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void NakamaApi::WriteTournamentRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v2/tournament/{tournament_id}"); + { const FString Encoded_TournamentId = FGenericPlatformHttp::UrlEncode(TournamentId); Endpoint = Endpoint.Replace(TEXT("{tournament_id}"), *Encoded_TournamentId); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + }TSharedPtr Body; + Body = Record.ToJson(); + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + FNakamaLeaderboardRecord Result = FNakamaLeaderboardRecord::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +// Module implementation +class FNakamaApiModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogNakama, Log, TEXT("NakamaApi module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogNakama, Log, TEXT("NakamaApi module shutting down")); + } +}; + +IMPLEMENT_MODULE(FNakamaApiModule, NakamaApi) diff --git a/Nakama/Source/NakamaApi/Private/NakamaRtTypes.cpp b/Nakama/Source/NakamaApi/Private/NakamaRtTypes.cpp new file mode 100644 index 000000000..33887910b --- /dev/null +++ b/Nakama/Source/NakamaApi/Private/NakamaRtTypes.cpp @@ -0,0 +1,2460 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtTypes.h" + +FNakamaRtEnvelope FNakamaRtEnvelope::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtEnvelope Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("cid"))) + { + Result.Cid = Json->GetStringField(TEXT("cid")); + } + return Result; +} + +TSharedPtr FNakamaRtEnvelope::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Cid.IsEmpty()) + { + Json->SetStringField(TEXT("cid"), Cid); + } + return Json; +} + +FNakamaRtUserPresence FNakamaRtUserPresence::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtUserPresence Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("session_id"))) + { + Result.SessionId = Json->GetStringField(TEXT("session_id")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("persistence"))) + { + Result.Persistence = Json->GetBoolField(TEXT("persistence")); + } + if (Json->HasField(TEXT("status"))) + { + Result.Status = Json->GetStringField(TEXT("status")); + } + return Result; +} + +TSharedPtr FNakamaRtUserPresence::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!SessionId.IsEmpty()) + { + Json->SetStringField(TEXT("session_id"), SessionId); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + Json->SetBoolField(TEXT("persistence"), Persistence); + if (!Status.IsEmpty()) + { + Json->SetStringField(TEXT("status"), Status); + } + return Json; +} + +FNakamaRtChannel FNakamaRtChannel::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannel Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("self"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("self"), NestedObj)) + { + Result.Self = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("room_name"))) + { + Result.RoomName = Json->GetStringField(TEXT("room_name")); + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_id_one"))) + { + Result.UserIdOne = Json->GetStringField(TEXT("user_id_one")); + } + if (Json->HasField(TEXT("user_id_two"))) + { + Result.UserIdTwo = Json->GetStringField(TEXT("user_id_two")); + } + return Result; +} + +TSharedPtr FNakamaRtChannel::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + return Json; +} + +FNakamaRtChannelJoin FNakamaRtChannelJoin::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelJoin Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("target"))) + { + Result.Target = Json->GetStringField(TEXT("target")); + } + if (Json->HasField(TEXT("type"))) + { + Result.Type = Json->GetIntegerField(TEXT("type")); + } + if (Json->HasField(TEXT("persistence"))) + { + Result.Persistence = Json->GetBoolField(TEXT("persistence")); + } + if (Json->HasField(TEXT("hidden"))) + { + Result.Hidden = Json->GetBoolField(TEXT("hidden")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelJoin::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Target.IsEmpty()) + { + Json->SetStringField(TEXT("target"), Target); + } + Json->SetNumberField(TEXT("type"), Type); + Json->SetBoolField(TEXT("persistence"), Persistence); + Json->SetBoolField(TEXT("hidden"), Hidden); + return Json; +} + +FNakamaRtChannelLeave FNakamaRtChannelLeave::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelLeave Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelLeave::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + return Json; +} + +FNakamaRtChannelMessageAck FNakamaRtChannelMessageAck::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelMessageAck Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("message_id"))) + { + Result.MessageId = Json->GetStringField(TEXT("message_id")); + } + if (Json->HasField(TEXT("code"))) + { + Result.Code = Json->GetIntegerField(TEXT("code")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("persistent"))) + { + Result.Persistent = Json->GetBoolField(TEXT("persistent")); + } + if (Json->HasField(TEXT("room_name"))) + { + Result.RoomName = Json->GetStringField(TEXT("room_name")); + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_id_one"))) + { + Result.UserIdOne = Json->GetStringField(TEXT("user_id_one")); + } + if (Json->HasField(TEXT("user_id_two"))) + { + Result.UserIdTwo = Json->GetStringField(TEXT("user_id_two")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelMessageAck::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + if (Code != 0) + { + Json->SetNumberField(TEXT("code"), Code); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + Json->SetBoolField(TEXT("persistent"), Persistent); + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + return Json; +} + +FNakamaRtChannelMessageSend FNakamaRtChannelMessageSend::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelMessageSend Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("content"))) + { + Result.Content = Json->GetStringField(TEXT("content")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelMessageSend::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + return Json; +} + +FNakamaRtChannelMessageUpdate FNakamaRtChannelMessageUpdate::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelMessageUpdate Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("message_id"))) + { + Result.MessageId = Json->GetStringField(TEXT("message_id")); + } + if (Json->HasField(TEXT("content"))) + { + Result.Content = Json->GetStringField(TEXT("content")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelMessageUpdate::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + return Json; +} + +FNakamaRtChannelMessageRemove FNakamaRtChannelMessageRemove::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelMessageRemove Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("message_id"))) + { + Result.MessageId = Json->GetStringField(TEXT("message_id")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelMessageRemove::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + return Json; +} + +FNakamaRtChannelPresenceEvent FNakamaRtChannelPresenceEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtChannelPresenceEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("joins"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("joins"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Joins.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("leaves"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaves"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaves.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("room_name"))) + { + Result.RoomName = Json->GetStringField(TEXT("room_name")); + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_id_one"))) + { + Result.UserIdOne = Json->GetStringField(TEXT("user_id_one")); + } + if (Json->HasField(TEXT("user_id_two"))) + { + Result.UserIdTwo = Json->GetStringField(TEXT("user_id_two")); + } + return Result; +} + +TSharedPtr FNakamaRtChannelPresenceEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + return Json; +} + +FNakamaRtError FNakamaRtError::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtError Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("code"))) + { + Result.Code = Json->GetIntegerField(TEXT("code")); + } + if (Json->HasField(TEXT("message"))) + { + Result.Message = Json->GetStringField(TEXT("message")); + } + if (Json->HasField(TEXT("context"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("context"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.Context.Add(Pair.Key, Pair.Value->AsString()); + + } + } + } + return Result; +} + +TSharedPtr FNakamaRtError::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("code"), Code); + if (!Message.IsEmpty()) + { + Json->SetStringField(TEXT("message"), Message); + } + if (Context.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Context) + { + + MapObj->SetStringField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("context"), MapObj); + } + return Json; +} + +FNakamaRtMatch FNakamaRtMatch::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatch Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + if (Json->HasField(TEXT("authoritative"))) + { + Result.Authoritative = Json->GetBoolField(TEXT("authoritative")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + if (Json->HasField(TEXT("size"))) + { + Result.Size = Json->GetIntegerField(TEXT("size")); + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("self"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("self"), NestedObj)) + { + Result.Self = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtMatch::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetBoolField(TEXT("authoritative"), Authoritative); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetNumberField(TEXT("size"), Size); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + return Json; +} + +FNakamaRtMatchCreate FNakamaRtMatchCreate::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchCreate Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchCreate::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + return Json; +} + +FNakamaRtMatchData FNakamaRtMatchData::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchData Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("op_code"))) + { + Result.OpCode = static_cast(Json->GetNumberField(TEXT("op_code"))); + } + if (Json->HasField(TEXT("data"))) + { + FString Base64 = Json->GetStringField(TEXT("data")); + if (!FBase64::Decode(Base64, Result.Data)) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } + if (Json->HasField(TEXT("reliable"))) + { + Result.Reliable = Json->GetBoolField(TEXT("reliable")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchData::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode("Data")); + Json->SetBoolField(TEXT("reliable"), Reliable); + return Json; +} + +FNakamaRtMatchDataSend FNakamaRtMatchDataSend::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchDataSend Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + if (Json->HasField(TEXT("op_code"))) + { + Result.OpCode = static_cast(Json->GetNumberField(TEXT("op_code"))); + } + if (Json->HasField(TEXT("data"))) + { + FString Base64 = Json->GetStringField(TEXT("data")); + if (!FBase64::Decode(Base64, Result.Data)) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("reliable"))) + { + Result.Reliable = Json->GetBoolField(TEXT("reliable")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchDataSend::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode("Data")); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetBoolField(TEXT("reliable"), Reliable); + return Json; +} + +FNakamaRtMatchJoin FNakamaRtMatchJoin::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchJoin Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("metadata"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("metadata"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.Metadata.Add(Pair.Key, Pair.Value->AsString()); + + } + } + } + return Result; +} + +TSharedPtr FNakamaRtMatchJoin::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Metadata.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Metadata) + { + + MapObj->SetStringField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("metadata"), MapObj); + } + return Json; +} + +FNakamaRtMatchLeave FNakamaRtMatchLeave::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchLeave Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchLeave::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + return Json; +} + +FNakamaRtMatchPresenceEvent FNakamaRtMatchPresenceEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchPresenceEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + if (Json->HasField(TEXT("joins"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("joins"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Joins.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("leaves"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaves"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaves.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtMatchPresenceEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + return Json; +} + +FNakamaRtMatchmakerAdd FNakamaRtMatchmakerAdd::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchmakerAdd Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("min_count"))) + { + Result.MinCount = Json->GetIntegerField(TEXT("min_count")); + } + if (Json->HasField(TEXT("max_count"))) + { + Result.MaxCount = Json->GetIntegerField(TEXT("max_count")); + } + if (Json->HasField(TEXT("query"))) + { + Result.Query = Json->GetStringField(TEXT("query")); + } + if (Json->HasField(TEXT("count_multiple"))) + { + Result.CountMultiple = Json->GetIntegerField(TEXT("count_multiple")); + } + if (Json->HasField(TEXT("string_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("string_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.StringProperties.Add(Pair.Key, Pair.Value->AsString()); + + } + } + } + if (Json->HasField(TEXT("numeric_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("numeric_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.NumericProperties.Add(Pair.Key, Pair.Value->AsNumber()); + + } + } + } + return Result; +} + +TSharedPtr FNakamaRtMatchmakerAdd::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("min_count"), MinCount); + Json->SetNumberField(TEXT("max_count"), MaxCount); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + if (CountMultiple != 0) + { + Json->SetNumberField(TEXT("count_multiple"), CountMultiple); + } + if (StringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StringProperties) + { + + MapObj->SetStringField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (NumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : NumericProperties) + { + + MapObj->SetNumberField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + return Json; +} + +FNakamaRtMatchmakerMatched_MatchmakerUser FNakamaRtMatchmakerMatched_MatchmakerUser::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchmakerMatched_MatchmakerUser Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("string_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("string_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.StringProperties.Add(Pair.Key, Pair.Value->AsString()); + + } + } + } + if (Json->HasField(TEXT("numeric_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("numeric_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.NumericProperties.Add(Pair.Key, Pair.Value->AsNumber()); + + } + } + } + return Result; +} + +TSharedPtr FNakamaRtMatchmakerMatched_MatchmakerUser::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (StringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StringProperties) + { + + MapObj->SetStringField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (NumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : NumericProperties) + { + + MapObj->SetNumberField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + return Json; +} + +FNakamaRtMatchmakerMatched FNakamaRtMatchmakerMatched::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchmakerMatched Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ticket"))) + { + Result.Ticket = Json->GetStringField(TEXT("ticket")); + } + if (Json->HasField(TEXT("users"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("users"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Users.Add(FNakamaRtMatchmakerMatched_MatchmakerUser::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("self"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("self"), NestedObj)) + { + Result.Self = FNakamaRtMatchmakerMatched_MatchmakerUser::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtMatchmakerMatched::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + if (Users.Num() > 0) + { + TArray> Array; + for (const auto& Item : Users) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("users"), Array); + } + Json->SetObjectField(TEXT("self"), Self.ToJson()); + return Json; +} + +FNakamaRtMatchmakerRemove FNakamaRtMatchmakerRemove::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchmakerRemove Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ticket"))) + { + Result.Ticket = Json->GetStringField(TEXT("ticket")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchmakerRemove::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + return Json; +} + +FNakamaRtMatchmakerTicket FNakamaRtMatchmakerTicket::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtMatchmakerTicket Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ticket"))) + { + Result.Ticket = Json->GetStringField(TEXT("ticket")); + } + return Result; +} + +TSharedPtr FNakamaRtMatchmakerTicket::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + return Json; +} + +FNakamaRtNotifications FNakamaRtNotifications::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtNotifications Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("notifications"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("notifications"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Notifications.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaRtNotifications::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Notifications.Num() > 0) + { + TArray> Array; + for (const auto& Item : Notifications) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("notifications"), Array); + } + return Json; +} + +FNakamaRtParty FNakamaRtParty::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtParty Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("hidden"))) + { + Result.Hidden = Json->GetBoolField(TEXT("hidden")); + } + if (Json->HasField(TEXT("max_size"))) + { + Result.MaxSize = Json->GetIntegerField(TEXT("max_size")); + } + if (Json->HasField(TEXT("self"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("self"), NestedObj)) + { + Result.Self = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("leader"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("leader"), NestedObj)) + { + Result.Leader = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + return Result; +} + +TSharedPtr FNakamaRtParty::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetBoolField(TEXT("hidden"), Hidden); + Json->SetNumberField(TEXT("max_size"), MaxSize); + Json->SetObjectField(TEXT("self"), Self.ToJson()); + Json->SetObjectField(TEXT("leader"), Leader.ToJson()); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + return Json; +} + +FNakamaRtPartyCreate FNakamaRtPartyCreate::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyCreate Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("max_size"))) + { + Result.MaxSize = Json->GetIntegerField(TEXT("max_size")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + if (Json->HasField(TEXT("hidden"))) + { + Result.Hidden = Json->GetBoolField(TEXT("hidden")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyCreate::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetBoolField(TEXT("open"), Open); + Json->SetNumberField(TEXT("max_size"), MaxSize); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetBoolField(TEXT("hidden"), Hidden); + return Json; +} + +FNakamaRtPartyUpdate FNakamaRtPartyUpdate::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyUpdate Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("hidden"))) + { + Result.Hidden = Json->GetBoolField(TEXT("hidden")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyUpdate::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetBoolField(TEXT("hidden"), Hidden); + return Json; +} + +FNakamaRtPartyJoin FNakamaRtPartyJoin::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyJoin Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyJoin::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + return Json; +} + +FNakamaRtPartyLeave FNakamaRtPartyLeave::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyLeave Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyLeave::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + return Json; +} + +FNakamaRtPartyPromote FNakamaRtPartyPromote::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyPromote Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyPromote::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + return Json; +} + +FNakamaRtPartyLeader FNakamaRtPartyLeader::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyLeader Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyLeader::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + return Json; +} + +FNakamaRtPartyAccept FNakamaRtPartyAccept::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyAccept Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyAccept::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + return Json; +} + +FNakamaRtPartyRemove FNakamaRtPartyRemove::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyRemove Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyRemove::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + return Json; +} + +FNakamaRtPartyClose FNakamaRtPartyClose::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyClose Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyClose::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + return Json; +} + +FNakamaRtPartyJoinRequestList FNakamaRtPartyJoinRequestList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyJoinRequestList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyJoinRequestList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + return Json; +} + +FNakamaRtPartyJoinRequest FNakamaRtPartyJoinRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyJoinRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyJoinRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + return Json; +} + +FNakamaRtPartyMatchmakerAdd FNakamaRtPartyMatchmakerAdd::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyMatchmakerAdd Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("min_count"))) + { + Result.MinCount = Json->GetIntegerField(TEXT("min_count")); + } + if (Json->HasField(TEXT("max_count"))) + { + Result.MaxCount = Json->GetIntegerField(TEXT("max_count")); + } + if (Json->HasField(TEXT("query"))) + { + Result.Query = Json->GetStringField(TEXT("query")); + } + if (Json->HasField(TEXT("count_multiple"))) + { + Result.CountMultiple = Json->GetIntegerField(TEXT("count_multiple")); + } + if (Json->HasField(TEXT("string_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("string_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.StringProperties.Add(Pair.Key, Pair.Value->AsString()); + + } + } + } + if (Json->HasField(TEXT("numeric_properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("numeric_properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + + Result.NumericProperties.Add(Pair.Key, Pair.Value->AsNumber()); + + } + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyMatchmakerAdd::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetNumberField(TEXT("min_count"), MinCount); + Json->SetNumberField(TEXT("max_count"), MaxCount); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + if (CountMultiple != 0) + { + Json->SetNumberField(TEXT("count_multiple"), CountMultiple); + } + if (StringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StringProperties) + { + + MapObj->SetStringField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (NumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : NumericProperties) + { + + MapObj->SetNumberField(Pair.Key, Pair.Value); + + } + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + return Json; +} + +FNakamaRtPartyMatchmakerRemove FNakamaRtPartyMatchmakerRemove::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyMatchmakerRemove Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("ticket"))) + { + Result.Ticket = Json->GetStringField(TEXT("ticket")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyMatchmakerRemove::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + return Json; +} + +FNakamaRtPartyMatchmakerTicket FNakamaRtPartyMatchmakerTicket::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyMatchmakerTicket Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("ticket"))) + { + Result.Ticket = Json->GetStringField(TEXT("ticket")); + } + return Result; +} + +TSharedPtr FNakamaRtPartyMatchmakerTicket::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (!Ticket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), Ticket); + } + return Json; +} + +FNakamaRtPartyData FNakamaRtPartyData::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyData Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("presence"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("presence"), NestedObj)) + { + Result.Presence = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("op_code"))) + { + Result.OpCode = static_cast(Json->GetNumberField(TEXT("op_code"))); + } + if (Json->HasField(TEXT("data"))) + { + FString Base64 = Json->GetStringField(TEXT("data")); + if (!FBase64::Decode(Base64, Result.Data)) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyData::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetObjectField(TEXT("presence"), Presence.ToJson()); + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode("Data")); + return Json; +} + +FNakamaRtPartyDataSend FNakamaRtPartyDataSend::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyDataSend Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("op_code"))) + { + Result.OpCode = static_cast(Json->GetNumberField(TEXT("op_code"))); + } + if (Json->HasField(TEXT("data"))) + { + FString Base64 = Json->GetStringField(TEXT("data")); + if (!FBase64::Decode(Base64, Result.Data)) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyDataSend::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetNumberField(TEXT("op_code"), OpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode("Data")); + return Json; +} + +FNakamaRtPartyPresenceEvent FNakamaRtPartyPresenceEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPartyPresenceEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("joins"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("joins"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Joins.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("leaves"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaves"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaves.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtPartyPresenceEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + return Json; +} + +FNakamaRtPing FNakamaRtPing::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPing Result; + if (!Json.IsValid()) + { + return Result; + } + return Result; +} + +TSharedPtr FNakamaRtPing::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + return Json; +} + +FNakamaRtPong FNakamaRtPong::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtPong Result; + if (!Json.IsValid()) + { + return Result; + } + return Result; +} + +TSharedPtr FNakamaRtPong::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + return Json; +} + +FNakamaRtStatus FNakamaRtStatus::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStatus Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("presences"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("presences"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Presences.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtStatus::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Presences.Num() > 0) + { + TArray> Array; + for (const auto& Item : Presences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + return Json; +} + +FNakamaRtStatusFollow FNakamaRtStatusFollow::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStatusFollow Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("usernames"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("usernames"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Usernames.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaRtStatusFollow::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + return Json; +} + +FNakamaRtStatusPresenceEvent FNakamaRtStatusPresenceEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStatusPresenceEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("joins"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("joins"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Joins.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("leaves"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaves"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaves.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtStatusPresenceEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + return Json; +} + +FNakamaRtStatusUnfollow FNakamaRtStatusUnfollow::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStatusUnfollow Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaRtStatusUnfollow::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaRtStatusUpdate FNakamaRtStatusUpdate::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStatusUpdate Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("status"))) + { + Result.Status = Json->GetStringField(TEXT("status")); + } + return Result; +} + +TSharedPtr FNakamaRtStatusUpdate::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Status.IsEmpty()) + { + Json->SetStringField(TEXT("status"), Status); + } + return Json; +} + +FNakamaRtStream FNakamaRtStream::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStream Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("mode"))) + { + Result.Mode = Json->GetIntegerField(TEXT("mode")); + } + if (Json->HasField(TEXT("subject"))) + { + Result.Subject = Json->GetStringField(TEXT("subject")); + } + if (Json->HasField(TEXT("subcontext"))) + { + Result.Subcontext = Json->GetStringField(TEXT("subcontext")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + return Result; +} + +TSharedPtr FNakamaRtStream::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("mode"), Mode); + if (!Subject.IsEmpty()) + { + Json->SetStringField(TEXT("subject"), Subject); + } + if (!Subcontext.IsEmpty()) + { + Json->SetStringField(TEXT("subcontext"), Subcontext); + } + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + return Json; +} + +FNakamaRtStreamData FNakamaRtStreamData::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStreamData Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("stream"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("stream"), NestedObj)) + { + Result.Stream = FNakamaRtStream::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("sender"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("sender"), NestedObj)) + { + Result.Sender = FNakamaRtUserPresence::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("data"))) + { + Result.Data = Json->GetStringField(TEXT("data")); + } + if (Json->HasField(TEXT("reliable"))) + { + Result.Reliable = Json->GetBoolField(TEXT("reliable")); + } + return Result; +} + +TSharedPtr FNakamaRtStreamData::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), Stream.ToJson()); + Json->SetObjectField(TEXT("sender"), Sender.ToJson()); + if (!Data.IsEmpty()) + { + Json->SetStringField(TEXT("data"), Data); + } + Json->SetBoolField(TEXT("reliable"), Reliable); + return Json; +} + +FNakamaRtStreamPresenceEvent FNakamaRtStreamPresenceEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRtStreamPresenceEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("stream"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("stream"), NestedObj)) + { + Result.Stream = FNakamaRtStream::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("joins"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("joins"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Joins.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("leaves"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaves"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaves.Add(FNakamaRtUserPresence::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaRtStreamPresenceEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), Stream.ToJson()); + if (Joins.Num() > 0) + { + TArray> Array; + for (const auto& Item : Joins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (Leaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + return Json; +} + + diff --git a/Nakama/Source/NakamaApi/Private/NakamaTypes.cpp b/Nakama/Source/NakamaApi/Private/NakamaTypes.cpp new file mode 100644 index 000000000..df35c1460 --- /dev/null +++ b/Nakama/Source/NakamaApi/Private/NakamaTypes.cpp @@ -0,0 +1,6025 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaTypes.h" + +// --- FNakamaSession JWT helpers --- + +bool FNakamaSession::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + // Base64url -> standard Base64 + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + // Pad to multiple of 4 + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void FNakamaSession::ParseTokens() noexcept +{ + UserId.Empty(); + Username.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + Vars.Empty(); + + // Parse auth token + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("uid"))) + { + UserId = TokenPayload->GetStringField(TEXT("uid")); + } + if (TokenPayload->HasField(TEXT("usn"))) + { + Username = TokenPayload->GetStringField(TEXT("usn")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + if (TokenPayload->HasField(TEXT("vrs"))) + { + const TSharedPtr* VrsObj; + if (TokenPayload->TryGetObjectField(TEXT("vrs"), VrsObj)) + { + for (const auto& Pair : (*VrsObj)->Values) + { + Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + } + + // Parse refresh token + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool FNakamaSession::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool FNakamaSession::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void FNakamaSession::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} + +FNakamaUser FNakamaUser::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUser Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("display_name"))) + { + Result.DisplayName = Json->GetStringField(TEXT("display_name")); + } + if (Json->HasField(TEXT("avatar_url"))) + { + Result.AvatarUrl = Json->GetStringField(TEXT("avatar_url")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("location"))) + { + Result.Location = Json->GetStringField(TEXT("location")); + } + if (Json->HasField(TEXT("timezone"))) + { + Result.Timezone = Json->GetStringField(TEXT("timezone")); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("facebook_id"))) + { + Result.FacebookId = Json->GetStringField(TEXT("facebook_id")); + } + if (Json->HasField(TEXT("google_id"))) + { + Result.GoogleId = Json->GetStringField(TEXT("google_id")); + } + if (Json->HasField(TEXT("gamecenter_id"))) + { + Result.GamecenterId = Json->GetStringField(TEXT("gamecenter_id")); + } + if (Json->HasField(TEXT("steam_id"))) + { + Result.SteamId = Json->GetStringField(TEXT("steam_id")); + } + if (Json->HasField(TEXT("online"))) + { + Result.Online = Json->GetBoolField(TEXT("online")); + } + if (Json->HasField(TEXT("edge_count"))) + { + Result.EdgeCount = Json->GetIntegerField(TEXT("edge_count")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("facebook_instant_game_id"))) + { + Result.FacebookInstantGameId = Json->GetStringField(TEXT("facebook_instant_game_id")); + } + if (Json->HasField(TEXT("apple_id"))) + { + Result.AppleId = Json->GetStringField(TEXT("apple_id")); + } + return Result; +} + +TSharedPtr FNakamaUser::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!DisplayName.IsEmpty()) + { + Json->SetStringField(TEXT("display_name"), DisplayName); + } + if (!AvatarUrl.IsEmpty()) + { + Json->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!Location.IsEmpty()) + { + Json->SetStringField(TEXT("location"), Location); + } + if (!Timezone.IsEmpty()) + { + Json->SetStringField(TEXT("timezone"), Timezone); + } + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + if (!FacebookId.IsEmpty()) + { + Json->SetStringField(TEXT("facebook_id"), FacebookId); + } + if (!GoogleId.IsEmpty()) + { + Json->SetStringField(TEXT("google_id"), GoogleId); + } + if (!GamecenterId.IsEmpty()) + { + Json->SetStringField(TEXT("gamecenter_id"), GamecenterId); + } + if (!SteamId.IsEmpty()) + { + Json->SetStringField(TEXT("steam_id"), SteamId); + } + Json->SetBoolField(TEXT("online"), Online); + Json->SetNumberField(TEXT("edge_count"), EdgeCount); + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (!FacebookInstantGameId.IsEmpty()) + { + Json->SetStringField(TEXT("facebook_instant_game_id"), FacebookInstantGameId); + } + if (!AppleId.IsEmpty()) + { + Json->SetStringField(TEXT("apple_id"), AppleId); + } + return Json; +} + +FNakamaAccountDevice FNakamaAccountDevice::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountDevice Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountDevice::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccount FNakamaAccount::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccount Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("user"), NestedObj)) + { + Result.User = FNakamaUser::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("wallet"))) + { + Result.Wallet = Json->GetStringField(TEXT("wallet")); + } + if (Json->HasField(TEXT("email"))) + { + Result.Email = Json->GetStringField(TEXT("email")); + } + if (Json->HasField(TEXT("devices"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("devices"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Devices.Add(FNakamaAccountDevice::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("custom_id"))) + { + Result.CustomId = Json->GetStringField(TEXT("custom_id")); + } + if (Json->HasField(TEXT("verify_time"))) + { + Result.VerifyTime = Json->GetStringField(TEXT("verify_time")); + } + if (Json->HasField(TEXT("disable_time"))) + { + Result.DisableTime = Json->GetStringField(TEXT("disable_time")); + } + return Result; +} + +TSharedPtr FNakamaAccount::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("user"), User.ToJson()); + if (!Wallet.IsEmpty()) + { + Json->SetStringField(TEXT("wallet"), Wallet); + } + if (!Email.IsEmpty()) + { + Json->SetStringField(TEXT("email"), Email); + } + if (Devices.Num() > 0) + { + TArray> Array; + for (const auto& Item : Devices) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("devices"), Array); + } + if (!CustomId.IsEmpty()) + { + Json->SetStringField(TEXT("custom_id"), CustomId); + } + if (!VerifyTime.IsEmpty()) + { + Json->SetStringField(TEXT("verify_time"), VerifyTime); + } + if (!DisableTime.IsEmpty()) + { + Json->SetStringField(TEXT("disable_time"), DisableTime); + } + return Json; +} + +FNakamaAccountRefresh FNakamaAccountRefresh::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountRefresh Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountRefresh::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountApple FNakamaAccountApple::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountApple Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountApple::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountCustom FNakamaAccountCustom::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountCustom Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountCustom::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountEmail FNakamaAccountEmail::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountEmail Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("email"))) + { + Result.Email = Json->GetStringField(TEXT("email")); + } + if (Json->HasField(TEXT("password"))) + { + Result.Password = Json->GetStringField(TEXT("password")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountEmail::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Email.IsEmpty()) + { + Json->SetStringField(TEXT("email"), Email); + } + if (!Password.IsEmpty()) + { + Json->SetStringField(TEXT("password"), Password); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountFacebook FNakamaAccountFacebook::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountFacebook Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountFacebook::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountFacebookInstantGame FNakamaAccountFacebookInstantGame::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountFacebookInstantGame Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("signed_player_info"))) + { + Result.SignedPlayerInfo = Json->GetStringField(TEXT("signed_player_info")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountFacebookInstantGame::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!SignedPlayerInfo.IsEmpty()) + { + Json->SetStringField(TEXT("signed_player_info"), SignedPlayerInfo); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountGameCenter FNakamaAccountGameCenter::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountGameCenter Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("player_id"))) + { + Result.PlayerId = Json->GetStringField(TEXT("player_id")); + } + if (Json->HasField(TEXT("bundle_id"))) + { + Result.BundleId = Json->GetStringField(TEXT("bundle_id")); + } + if (Json->HasField(TEXT("timestamp_seconds"))) + { + Result.TimestampSeconds = static_cast(Json->GetNumberField(TEXT("timestamp_seconds"))); + } + if (Json->HasField(TEXT("salt"))) + { + Result.Salt = Json->GetStringField(TEXT("salt")); + } + if (Json->HasField(TEXT("signature"))) + { + Result.Signature = Json->GetStringField(TEXT("signature")); + } + if (Json->HasField(TEXT("public_key_url"))) + { + Result.PublicKeyUrl = Json->GetStringField(TEXT("public_key_url")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountGameCenter::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PlayerId.IsEmpty()) + { + Json->SetStringField(TEXT("player_id"), PlayerId); + } + if (!BundleId.IsEmpty()) + { + Json->SetStringField(TEXT("bundle_id"), BundleId); + } + Json->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); + if (!Salt.IsEmpty()) + { + Json->SetStringField(TEXT("salt"), Salt); + } + if (!Signature.IsEmpty()) + { + Json->SetStringField(TEXT("signature"), Signature); + } + if (!PublicKeyUrl.IsEmpty()) + { + Json->SetStringField(TEXT("public_key_url"), PublicKeyUrl); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountGoogle FNakamaAccountGoogle::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountGoogle Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountGoogle::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAccountSteam FNakamaAccountSteam::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAccountSteam Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAccountSteam::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaAddFriendsRequest FNakamaAddFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAddFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Ids.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("usernames"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("usernames"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Usernames.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + return Result; +} + +TSharedPtr FNakamaAddFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Ids.Num() > 0) + { + TArray> Array; + for (const auto& Item : Ids) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + return Json; +} + +FNakamaAddGroupUsersRequest FNakamaAddGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAddGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaAddGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaSessionRefreshRequest FNakamaSessionRefreshRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaSessionRefreshRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("vars"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("vars"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaSessionRefreshRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (Vars.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Vars) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("vars"), MapObj); + } + return Json; +} + +FNakamaSessionLogoutRequest FNakamaSessionLogoutRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaSessionLogoutRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("refresh_token"))) + { + Result.RefreshToken = Json->GetStringField(TEXT("refresh_token")); + } + return Result; +} + +TSharedPtr FNakamaSessionLogoutRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Json->SetStringField(TEXT("refresh_token"), RefreshToken); + } + return Json; +} + +FNakamaAuthenticateAppleRequest FNakamaAuthenticateAppleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateAppleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountApple::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateAppleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateCustomRequest FNakamaAuthenticateCustomRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateCustomRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountCustom::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateCustomRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateDeviceRequest FNakamaAuthenticateDeviceRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateDeviceRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountDevice::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateDeviceRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateEmailRequest FNakamaAuthenticateEmailRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateEmailRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountEmail::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateEmailRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateFacebookRequest FNakamaAuthenticateFacebookRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateFacebookRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountFacebook::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("sync"))) + { + Result.Sync = Json->GetBoolField(TEXT("sync")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateFacebookRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + Json->SetBoolField(TEXT("sync"), Sync); + return Json; +} + +FNakamaAuthenticateFacebookInstantGameRequest FNakamaAuthenticateFacebookInstantGameRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateFacebookInstantGameRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountFacebookInstantGame::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateFacebookInstantGameRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateGameCenterRequest FNakamaAuthenticateGameCenterRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateGameCenterRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountGameCenter::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateGameCenterRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateGoogleRequest FNakamaAuthenticateGoogleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateGoogleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountGoogle::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateGoogleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + return Json; +} + +FNakamaAuthenticateSteamRequest FNakamaAuthenticateSteamRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaAuthenticateSteamRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountSteam::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("create"))) + { + Result.Create = Json->GetBoolField(TEXT("create")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("sync"))) + { + Result.Sync = Json->GetBoolField(TEXT("sync")); + } + return Result; +} + +TSharedPtr FNakamaAuthenticateSteamRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("create"), Create); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + Json->SetBoolField(TEXT("sync"), Sync); + return Json; +} + +FNakamaBanGroupUsersRequest FNakamaBanGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaBanGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaBanGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaBlockFriendsRequest FNakamaBlockFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaBlockFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Ids.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("usernames"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("usernames"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Usernames.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaBlockFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Ids.Num() > 0) + { + TArray> Array; + for (const auto& Item : Ids) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + return Json; +} + +FNakamaChannelMessage FNakamaChannelMessage::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaChannelMessage Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("message_id"))) + { + Result.MessageId = Json->GetStringField(TEXT("message_id")); + } + if (Json->HasField(TEXT("code"))) + { + Result.Code = Json->GetIntegerField(TEXT("code")); + } + if (Json->HasField(TEXT("sender_id"))) + { + Result.SenderId = Json->GetStringField(TEXT("sender_id")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("content"))) + { + Result.Content = Json->GetStringField(TEXT("content")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("persistent"))) + { + Result.Persistent = Json->GetBoolField(TEXT("persistent")); + } + if (Json->HasField(TEXT("room_name"))) + { + Result.RoomName = Json->GetStringField(TEXT("room_name")); + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_id_one"))) + { + Result.UserIdOne = Json->GetStringField(TEXT("user_id_one")); + } + if (Json->HasField(TEXT("user_id_two"))) + { + Result.UserIdTwo = Json->GetStringField(TEXT("user_id_two")); + } + return Result; +} + +TSharedPtr FNakamaChannelMessage::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + if (!MessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), MessageId); + } + Json->SetNumberField(TEXT("code"), Code); + if (!SenderId.IsEmpty()) + { + Json->SetStringField(TEXT("sender_id"), SenderId); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + Json->SetBoolField(TEXT("persistent"), Persistent); + if (!RoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), RoomName); + } + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!UserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), UserIdOne); + } + if (!UserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), UserIdTwo); + } + return Json; +} + +FNakamaChannelMessageList FNakamaChannelMessageList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaChannelMessageList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("messages"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("messages"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Messages.Add(FNakamaChannelMessage::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("next_cursor"))) + { + Result.NextCursor = Json->GetStringField(TEXT("next_cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + if (Json->HasField(TEXT("cacheable_cursor"))) + { + Result.CacheableCursor = Json->GetStringField(TEXT("cacheable_cursor")); + } + return Result; +} + +TSharedPtr FNakamaChannelMessageList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Messages.Num() > 0) + { + TArray> Array; + for (const auto& Item : Messages) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("messages"), Array); + } + if (!NextCursor.IsEmpty()) + { + Json->SetStringField(TEXT("next_cursor"), NextCursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + if (!CacheableCursor.IsEmpty()) + { + Json->SetStringField(TEXT("cacheable_cursor"), CacheableCursor); + } + return Json; +} + +FNakamaCreateGroupRequest FNakamaCreateGroupRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaCreateGroupRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("description"))) + { + Result.Description = Json->GetStringField(TEXT("description")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("avatar_url"))) + { + Result.AvatarUrl = Json->GetStringField(TEXT("avatar_url")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("max_count"))) + { + Result.MaxCount = Json->GetIntegerField(TEXT("max_count")); + } + return Result; +} + +TSharedPtr FNakamaCreateGroupRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Json->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Json->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetNumberField(TEXT("max_count"), MaxCount); + return Json; +} + +FNakamaDeleteFriendsRequest FNakamaDeleteFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Ids.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("usernames"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("usernames"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Usernames.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaDeleteFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Ids.Num() > 0) + { + TArray> Array; + for (const auto& Item : Ids) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + return Json; +} + +FNakamaDeleteGroupRequest FNakamaDeleteGroupRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteGroupRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + return Result; +} + +TSharedPtr FNakamaDeleteGroupRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + return Json; +} + +FNakamaDeleteLeaderboardRecordRequest FNakamaDeleteLeaderboardRecordRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteLeaderboardRecordRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboard_id"))) + { + Result.LeaderboardId = Json->GetStringField(TEXT("leaderboard_id")); + } + return Result; +} + +TSharedPtr FNakamaDeleteLeaderboardRecordRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!LeaderboardId.IsEmpty()) + { + Json->SetStringField(TEXT("leaderboard_id"), LeaderboardId); + } + return Json; +} + +FNakamaDeleteNotificationsRequest FNakamaDeleteNotificationsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteNotificationsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Ids.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaDeleteNotificationsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Ids.Num() > 0) + { + TArray> Array; + for (const auto& Item : Ids) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("ids"), Array); + } + return Json; +} + +FNakamaDeleteTournamentRecordRequest FNakamaDeleteTournamentRecordRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteTournamentRecordRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournament_id"))) + { + Result.TournamentId = Json->GetStringField(TEXT("tournament_id")); + } + return Result; +} + +TSharedPtr FNakamaDeleteTournamentRecordRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!TournamentId.IsEmpty()) + { + Json->SetStringField(TEXT("tournament_id"), TournamentId); + } + return Json; +} + +FNakamaDeleteStorageObjectId FNakamaDeleteStorageObjectId::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteStorageObjectId Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("key"))) + { + Result.Key = Json->GetStringField(TEXT("key")); + } + if (Json->HasField(TEXT("version"))) + { + Result.Version = Json->GetStringField(TEXT("version")); + } + return Result; +} + +TSharedPtr FNakamaDeleteStorageObjectId::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + if (!Key.IsEmpty()) + { + Json->SetStringField(TEXT("key"), Key); + } + if (!Version.IsEmpty()) + { + Json->SetStringField(TEXT("version"), Version); + } + return Json; +} + +FNakamaDeleteStorageObjectsRequest FNakamaDeleteStorageObjectsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDeleteStorageObjectsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("object_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("object_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ObjectIds.Add(FNakamaDeleteStorageObjectId::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaDeleteStorageObjectsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("object_ids"), Array); + } + return Json; +} + +FNakamaEvent FNakamaEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("timestamp"))) + { + Result.Timestamp = Json->GetStringField(TEXT("timestamp")); + } + if (Json->HasField(TEXT("external"))) + { + Result.External = Json->GetBoolField(TEXT("external")); + } + if (Json->HasField(TEXT("properties"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("properties"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Properties.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Timestamp.IsEmpty()) + { + Json->SetStringField(TEXT("timestamp"), Timestamp); + } + Json->SetBoolField(TEXT("external"), External); + if (Properties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Properties) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("properties"), MapObj); + } + return Json; +} + +FNakamaFriend FNakamaFriend::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaFriend Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("user"), NestedObj)) + { + Result.User = FNakamaUser::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + return Result; +} + +TSharedPtr FNakamaFriend::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("user"), User.ToJson()); + Json->SetNumberField(TEXT("state"), State); + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + return Json; +} + +FNakamaFriendList FNakamaFriendList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaFriendList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("friends"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("friends"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Friends.Add(FNakamaFriend::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaFriendList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Friends.Num() > 0) + { + TArray> Array; + for (const auto& Item : Friends) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("friends"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaFriendsOfFriendsList_FriendOfFriend FNakamaFriendsOfFriendsList_FriendOfFriend::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaFriendsOfFriendsList_FriendOfFriend Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("referrer"))) + { + Result.Referrer = Json->GetStringField(TEXT("referrer")); + } + if (Json->HasField(TEXT("user"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("user"), NestedObj)) + { + Result.User = FNakamaUser::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaFriendsOfFriendsList_FriendOfFriend::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Referrer.IsEmpty()) + { + Json->SetStringField(TEXT("referrer"), Referrer); + } + Json->SetObjectField(TEXT("user"), User.ToJson()); + return Json; +} + +FNakamaFriendsOfFriendsList FNakamaFriendsOfFriendsList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaFriendsOfFriendsList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("friends_of_friends"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("friends_of_friends"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.FriendsOfFriends.Add(FNakamaFriendsOfFriendsList_FriendOfFriend::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaFriendsOfFriendsList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (FriendsOfFriends.Num() > 0) + { + TArray> Array; + for (const auto& Item : FriendsOfFriends) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("friends_of_friends"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaGetUsersRequest FNakamaGetUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGetUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Ids.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("usernames"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("usernames"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Usernames.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("facebook_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("facebook_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.FacebookIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaGetUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Ids.Num() > 0) + { + TArray> Array; + for (const auto& Item : Ids) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("ids"), Array); + } + if (Usernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : Usernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + if (FacebookIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : FacebookIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("facebook_ids"), Array); + } + return Json; +} + +FNakamaGetSubscriptionRequest FNakamaGetSubscriptionRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGetSubscriptionRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("product_id"))) + { + Result.ProductId = Json->GetStringField(TEXT("product_id")); + } + return Result; +} + +TSharedPtr FNakamaGetSubscriptionRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ProductId.IsEmpty()) + { + Json->SetStringField(TEXT("product_id"), ProductId); + } + return Json; +} + +FNakamaGroup FNakamaGroup::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGroup Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("creator_id"))) + { + Result.CreatorId = Json->GetStringField(TEXT("creator_id")); + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("description"))) + { + Result.Description = Json->GetStringField(TEXT("description")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("avatar_url"))) + { + Result.AvatarUrl = Json->GetStringField(TEXT("avatar_url")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("edge_count"))) + { + Result.EdgeCount = Json->GetIntegerField(TEXT("edge_count")); + } + if (Json->HasField(TEXT("max_count"))) + { + Result.MaxCount = Json->GetIntegerField(TEXT("max_count")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + return Result; +} + +TSharedPtr FNakamaGroup::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!CreatorId.IsEmpty()) + { + Json->SetStringField(TEXT("creator_id"), CreatorId); + } + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Json->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + if (!AvatarUrl.IsEmpty()) + { + Json->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetNumberField(TEXT("edge_count"), EdgeCount); + Json->SetNumberField(TEXT("max_count"), MaxCount); + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + return Json; +} + +FNakamaGroupList FNakamaGroupList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGroupList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("groups"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("groups"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Groups.Add(FNakamaGroup::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaGroupList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Groups.Num() > 0) + { + TArray> Array; + for (const auto& Item : Groups) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("groups"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaGroupUserList_GroupUser FNakamaGroupUserList_GroupUser::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGroupUserList_GroupUser Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("user"), NestedObj)) + { + Result.User = FNakamaUser::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + return Result; +} + +TSharedPtr FNakamaGroupUserList_GroupUser::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("user"), User.ToJson()); + Json->SetNumberField(TEXT("state"), State); + return Json; +} + +FNakamaGroupUserList FNakamaGroupUserList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaGroupUserList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_users"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("group_users"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.GroupUsers.Add(FNakamaGroupUserList_GroupUser::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaGroupUserList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (GroupUsers.Num() > 0) + { + TArray> Array; + for (const auto& Item : GroupUsers) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("group_users"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaImportFacebookFriendsRequest FNakamaImportFacebookFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaImportFacebookFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountFacebook::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("reset"))) + { + Result.Reset = Json->GetBoolField(TEXT("reset")); + } + return Result; +} + +TSharedPtr FNakamaImportFacebookFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("reset"), Reset); + return Json; +} + +FNakamaImportSteamFriendsRequest FNakamaImportSteamFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaImportSteamFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountSteam::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("reset"))) + { + Result.Reset = Json->GetBoolField(TEXT("reset")); + } + return Result; +} + +TSharedPtr FNakamaImportSteamFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("reset"), Reset); + return Json; +} + +FNakamaJoinGroupRequest FNakamaJoinGroupRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaJoinGroupRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + return Result; +} + +TSharedPtr FNakamaJoinGroupRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + return Json; +} + +FNakamaJoinTournamentRequest FNakamaJoinTournamentRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaJoinTournamentRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournament_id"))) + { + Result.TournamentId = Json->GetStringField(TEXT("tournament_id")); + } + return Result; +} + +TSharedPtr FNakamaJoinTournamentRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!TournamentId.IsEmpty()) + { + Json->SetStringField(TEXT("tournament_id"), TournamentId); + } + return Json; +} + +FNakamaKickGroupUsersRequest FNakamaKickGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaKickGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaKickGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaLeaderboard FNakamaLeaderboard::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLeaderboard Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("sort_order"))) + { + Result.SortOrder = static_cast(Json->GetNumberField(TEXT("sort_order"))); + } + if (Json->HasField(TEXT("operator"))) + { + Result.Operator = static_cast(Json->GetIntegerField(TEXT("operator"))); + } + if (Json->HasField(TEXT("prev_reset"))) + { + Result.PrevReset = static_cast(Json->GetNumberField(TEXT("prev_reset"))); + } + if (Json->HasField(TEXT("next_reset"))) + { + Result.NextReset = static_cast(Json->GetNumberField(TEXT("next_reset"))); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("authoritative"))) + { + Result.Authoritative = Json->GetBoolField(TEXT("authoritative")); + } + return Result; +} + +TSharedPtr FNakamaLeaderboard::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + Json->SetNumberField(TEXT("sort_order"), SortOrder); + Json->SetNumberField(TEXT("operator"), static_cast(Operator)); + Json->SetNumberField(TEXT("prev_reset"), PrevReset); + Json->SetNumberField(TEXT("next_reset"), NextReset); + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + Json->SetBoolField(TEXT("authoritative"), Authoritative); + return Json; +} + +FNakamaLeaderboardList FNakamaLeaderboardList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLeaderboardList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboards"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("leaderboards"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Leaderboards.Add(FNakamaLeaderboard::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaLeaderboardList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Leaderboards.Num() > 0) + { + TArray> Array; + for (const auto& Item : Leaderboards) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaderboards"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaLeaderboardRecord FNakamaLeaderboardRecord::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLeaderboardRecord Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboard_id"))) + { + Result.LeaderboardId = Json->GetStringField(TEXT("leaderboard_id")); + } + if (Json->HasField(TEXT("owner_id"))) + { + Result.OwnerId = Json->GetStringField(TEXT("owner_id")); + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("score"))) + { + Result.Score = static_cast(Json->GetNumberField(TEXT("score"))); + } + if (Json->HasField(TEXT("subscore"))) + { + Result.Subscore = static_cast(Json->GetNumberField(TEXT("subscore"))); + } + if (Json->HasField(TEXT("num_score"))) + { + Result.NumScore = Json->GetIntegerField(TEXT("num_score")); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("expiry_time"))) + { + Result.ExpiryTime = Json->GetStringField(TEXT("expiry_time")); + } + if (Json->HasField(TEXT("rank"))) + { + Result.Rank = static_cast(Json->GetNumberField(TEXT("rank"))); + } + if (Json->HasField(TEXT("max_num_score"))) + { + Result.MaxNumScore = static_cast(Json->GetNumberField(TEXT("max_num_score"))); + } + return Result; +} + +TSharedPtr FNakamaLeaderboardRecord::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!LeaderboardId.IsEmpty()) + { + Json->SetStringField(TEXT("leaderboard_id"), LeaderboardId); + } + if (!OwnerId.IsEmpty()) + { + Json->SetStringField(TEXT("owner_id"), OwnerId); + } + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + Json->SetNumberField(TEXT("score"), Score); + Json->SetNumberField(TEXT("subscore"), Subscore); + Json->SetNumberField(TEXT("num_score"), NumScore); + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (!ExpiryTime.IsEmpty()) + { + Json->SetStringField(TEXT("expiry_time"), ExpiryTime); + } + Json->SetNumberField(TEXT("rank"), Rank); + Json->SetNumberField(TEXT("max_num_score"), MaxNumScore); + return Json; +} + +FNakamaLeaderboardRecordList FNakamaLeaderboardRecordList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLeaderboardRecordList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("records"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("records"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Records.Add(FNakamaLeaderboardRecord::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("owner_records"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("owner_records"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.OwnerRecords.Add(FNakamaLeaderboardRecord::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("next_cursor"))) + { + Result.NextCursor = Json->GetStringField(TEXT("next_cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + if (Json->HasField(TEXT("rank_count"))) + { + Result.RankCount = static_cast(Json->GetNumberField(TEXT("rank_count"))); + } + return Result; +} + +TSharedPtr FNakamaLeaderboardRecordList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Records.Num() > 0) + { + TArray> Array; + for (const auto& Item : Records) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("records"), Array); + } + if (OwnerRecords.Num() > 0) + { + TArray> Array; + for (const auto& Item : OwnerRecords) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("owner_records"), Array); + } + if (!NextCursor.IsEmpty()) + { + Json->SetStringField(TEXT("next_cursor"), NextCursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + Json->SetNumberField(TEXT("rank_count"), RankCount); + return Json; +} + +FNakamaLeaveGroupRequest FNakamaLeaveGroupRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLeaveGroupRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + return Result; +} + +TSharedPtr FNakamaLeaveGroupRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + return Json; +} + +FNakamaLinkFacebookRequest FNakamaLinkFacebookRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLinkFacebookRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountFacebook::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("sync"))) + { + Result.Sync = Json->GetBoolField(TEXT("sync")); + } + return Result; +} + +TSharedPtr FNakamaLinkFacebookRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("sync"), Sync); + return Json; +} + +FNakamaLinkSteamRequest FNakamaLinkSteamRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaLinkSteamRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("account"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("account"), NestedObj)) + { + Result.Account = FNakamaAccountSteam::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("sync"))) + { + Result.Sync = Json->GetBoolField(TEXT("sync")); + } + return Result; +} + +TSharedPtr FNakamaLinkSteamRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("account"), Account.ToJson()); + Json->SetBoolField(TEXT("sync"), Sync); + return Json; +} + +FNakamaListChannelMessagesRequest FNakamaListChannelMessagesRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListChannelMessagesRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("channel_id"))) + { + Result.ChannelId = Json->GetStringField(TEXT("channel_id")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("forward"))) + { + Result.Forward = Json->GetBoolField(TEXT("forward")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListChannelMessagesRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), ChannelId); + } + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetBoolField(TEXT("forward"), Forward); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListFriendsRequest FNakamaListFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetNumberField(TEXT("state"), State); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListFriendsOfFriendsRequest FNakamaListFriendsOfFriendsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListFriendsOfFriendsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListFriendsOfFriendsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListGroupsRequest FNakamaListGroupsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListGroupsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("members"))) + { + Result.Members = Json->GetIntegerField(TEXT("members")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + return Result; +} + +TSharedPtr FNakamaListGroupsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + Json->SetNumberField(TEXT("members"), Members); + Json->SetBoolField(TEXT("open"), Open); + return Json; +} + +FNakamaListGroupUsersRequest FNakamaListGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetNumberField(TEXT("state"), State); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListLeaderboardRecordsAroundOwnerRequest FNakamaListLeaderboardRecordsAroundOwnerRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListLeaderboardRecordsAroundOwnerRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboard_id"))) + { + Result.LeaderboardId = Json->GetStringField(TEXT("leaderboard_id")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("owner_id"))) + { + Result.OwnerId = Json->GetStringField(TEXT("owner_id")); + } + if (Json->HasField(TEXT("expiry"))) + { + Result.Expiry = static_cast(Json->GetNumberField(TEXT("expiry"))); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListLeaderboardRecordsAroundOwnerRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!LeaderboardId.IsEmpty()) + { + Json->SetStringField(TEXT("leaderboard_id"), LeaderboardId); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!OwnerId.IsEmpty()) + { + Json->SetStringField(TEXT("owner_id"), OwnerId); + } + Json->SetNumberField(TEXT("expiry"), Expiry); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListLeaderboardRecordsRequest FNakamaListLeaderboardRecordsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListLeaderboardRecordsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboard_id"))) + { + Result.LeaderboardId = Json->GetStringField(TEXT("leaderboard_id")); + } + if (Json->HasField(TEXT("owner_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("owner_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.OwnerIds.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("expiry"))) + { + Result.Expiry = static_cast(Json->GetNumberField(TEXT("expiry"))); + } + return Result; +} + +TSharedPtr FNakamaListLeaderboardRecordsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!LeaderboardId.IsEmpty()) + { + Json->SetStringField(TEXT("leaderboard_id"), LeaderboardId); + } + if (OwnerIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : OwnerIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("owner_ids"), Array); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + Json->SetNumberField(TEXT("expiry"), Expiry); + return Json; +} + +FNakamaListMatchesRequest FNakamaListMatchesRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListMatchesRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("authoritative"))) + { + Result.Authoritative = Json->GetBoolField(TEXT("authoritative")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + if (Json->HasField(TEXT("min_size"))) + { + Result.MinSize = Json->GetIntegerField(TEXT("min_size")); + } + if (Json->HasField(TEXT("max_size"))) + { + Result.MaxSize = Json->GetIntegerField(TEXT("max_size")); + } + if (Json->HasField(TEXT("query"))) + { + Result.Query = Json->GetStringField(TEXT("query")); + } + return Result; +} + +TSharedPtr FNakamaListMatchesRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetBoolField(TEXT("authoritative"), Authoritative); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetNumberField(TEXT("min_size"), MinSize); + Json->SetNumberField(TEXT("max_size"), MaxSize); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + return Json; +} + +FNakamaListNotificationsRequest FNakamaListNotificationsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListNotificationsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cacheable_cursor"))) + { + Result.CacheableCursor = Json->GetStringField(TEXT("cacheable_cursor")); + } + return Result; +} + +TSharedPtr FNakamaListNotificationsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + if (!CacheableCursor.IsEmpty()) + { + Json->SetStringField(TEXT("cacheable_cursor"), CacheableCursor); + } + return Json; +} + +FNakamaListStorageObjectsRequest FNakamaListStorageObjectsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListStorageObjectsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListStorageObjectsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListSubscriptionsRequest FNakamaListSubscriptionsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListSubscriptionsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListSubscriptionsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListTournamentRecordsAroundOwnerRequest FNakamaListTournamentRecordsAroundOwnerRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListTournamentRecordsAroundOwnerRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournament_id"))) + { + Result.TournamentId = Json->GetStringField(TEXT("tournament_id")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("owner_id"))) + { + Result.OwnerId = Json->GetStringField(TEXT("owner_id")); + } + if (Json->HasField(TEXT("expiry"))) + { + Result.Expiry = static_cast(Json->GetNumberField(TEXT("expiry"))); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListTournamentRecordsAroundOwnerRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!TournamentId.IsEmpty()) + { + Json->SetStringField(TEXT("tournament_id"), TournamentId); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!OwnerId.IsEmpty()) + { + Json->SetStringField(TEXT("owner_id"), OwnerId); + } + Json->SetNumberField(TEXT("expiry"), Expiry); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListTournamentRecordsRequest FNakamaListTournamentRecordsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListTournamentRecordsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournament_id"))) + { + Result.TournamentId = Json->GetStringField(TEXT("tournament_id")); + } + if (Json->HasField(TEXT("owner_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("owner_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.OwnerIds.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("expiry"))) + { + Result.Expiry = static_cast(Json->GetNumberField(TEXT("expiry"))); + } + return Result; +} + +TSharedPtr FNakamaListTournamentRecordsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!TournamentId.IsEmpty()) + { + Json->SetStringField(TEXT("tournament_id"), TournamentId); + } + if (OwnerIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : OwnerIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("owner_ids"), Array); + } + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + Json->SetNumberField(TEXT("expiry"), Expiry); + return Json; +} + +FNakamaListTournamentsRequest FNakamaListTournamentsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListTournamentsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("category_start"))) + { + Result.CategoryStart = Json->GetIntegerField(TEXT("category_start")); + } + if (Json->HasField(TEXT("category_end"))) + { + Result.CategoryEnd = Json->GetIntegerField(TEXT("category_end")); + } + if (Json->HasField(TEXT("start_time"))) + { + Result.StartTime = Json->GetIntegerField(TEXT("start_time")); + } + if (Json->HasField(TEXT("end_time"))) + { + Result.EndTime = Json->GetIntegerField(TEXT("end_time")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListTournamentsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("category_start"), CategoryStart); + Json->SetNumberField(TEXT("category_end"), CategoryEnd); + Json->SetNumberField(TEXT("start_time"), StartTime); + Json->SetNumberField(TEXT("end_time"), EndTime); + Json->SetNumberField(TEXT("limit"), Limit); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaListUserGroupsRequest FNakamaListUserGroupsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListUserGroupsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListUserGroupsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetNumberField(TEXT("state"), State); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaMatch FNakamaMatch::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaMatch Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("match_id"))) + { + Result.MatchId = Json->GetStringField(TEXT("match_id")); + } + if (Json->HasField(TEXT("authoritative"))) + { + Result.Authoritative = Json->GetBoolField(TEXT("authoritative")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + if (Json->HasField(TEXT("size"))) + { + Result.Size = Json->GetIntegerField(TEXT("size")); + } + if (Json->HasField(TEXT("tick_rate"))) + { + Result.TickRate = Json->GetIntegerField(TEXT("tick_rate")); + } + if (Json->HasField(TEXT("handler_name"))) + { + Result.HandlerName = Json->GetStringField(TEXT("handler_name")); + } + return Result; +} + +TSharedPtr FNakamaMatch::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!MatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), MatchId); + } + Json->SetBoolField(TEXT("authoritative"), Authoritative); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + Json->SetNumberField(TEXT("size"), Size); + Json->SetNumberField(TEXT("tick_rate"), TickRate); + if (!HandlerName.IsEmpty()) + { + Json->SetStringField(TEXT("handler_name"), HandlerName); + } + return Json; +} + +FNakamaMatchList FNakamaMatchList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaMatchList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("matches"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("matches"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Matches.Add(FNakamaMatch::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaMatchList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Matches.Num() > 0) + { + TArray> Array; + for (const auto& Item : Matches) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("matches"), Array); + } + return Json; +} + +FNakamaMatchmakerCompletionStats FNakamaMatchmakerCompletionStats::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaMatchmakerCompletionStats Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("complete_time"))) + { + Result.CompleteTime = Json->GetStringField(TEXT("complete_time")); + } + return Result; +} + +TSharedPtr FNakamaMatchmakerCompletionStats::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!CompleteTime.IsEmpty()) + { + Json->SetStringField(TEXT("complete_time"), CompleteTime); + } + return Json; +} + +FNakamaMatchmakerStats FNakamaMatchmakerStats::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaMatchmakerStats Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("ticket_count"))) + { + Result.TicketCount = Json->GetIntegerField(TEXT("ticket_count")); + } + if (Json->HasField(TEXT("oldest_ticket_create_time"))) + { + Result.OldestTicketCreateTime = Json->GetStringField(TEXT("oldest_ticket_create_time")); + } + if (Json->HasField(TEXT("completions"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("completions"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Completions.Add(FNakamaMatchmakerCompletionStats::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaMatchmakerStats::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("ticket_count"), TicketCount); + if (!OldestTicketCreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("oldest_ticket_create_time"), OldestTicketCreateTime); + } + if (Completions.Num() > 0) + { + TArray> Array; + for (const auto& Item : Completions) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("completions"), Array); + } + return Json; +} + +FNakamaNotification FNakamaNotification::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaNotification Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("subject"))) + { + Result.Subject = Json->GetStringField(TEXT("subject")); + } + if (Json->HasField(TEXT("content"))) + { + Result.Content = Json->GetStringField(TEXT("content")); + } + if (Json->HasField(TEXT("code"))) + { + Result.Code = Json->GetIntegerField(TEXT("code")); + } + if (Json->HasField(TEXT("sender_id"))) + { + Result.SenderId = Json->GetStringField(TEXT("sender_id")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("persistent"))) + { + Result.Persistent = Json->GetBoolField(TEXT("persistent")); + } + return Result; +} + +TSharedPtr FNakamaNotification::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Subject.IsEmpty()) + { + Json->SetStringField(TEXT("subject"), Subject); + } + if (!Content.IsEmpty()) + { + Json->SetStringField(TEXT("content"), Content); + } + Json->SetNumberField(TEXT("code"), Code); + if (!SenderId.IsEmpty()) + { + Json->SetStringField(TEXT("sender_id"), SenderId); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + Json->SetBoolField(TEXT("persistent"), Persistent); + return Json; +} + +FNakamaNotificationList FNakamaNotificationList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaNotificationList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("notifications"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("notifications"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Notifications.Add(FNakamaNotification::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cacheable_cursor"))) + { + Result.CacheableCursor = Json->GetStringField(TEXT("cacheable_cursor")); + } + return Result; +} + +TSharedPtr FNakamaNotificationList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Notifications.Num() > 0) + { + TArray> Array; + for (const auto& Item : Notifications) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("notifications"), Array); + } + if (!CacheableCursor.IsEmpty()) + { + Json->SetStringField(TEXT("cacheable_cursor"), CacheableCursor); + } + return Json; +} + +FNakamaPromoteGroupUsersRequest FNakamaPromoteGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaPromoteGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaPromoteGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaDemoteGroupUsersRequest FNakamaDemoteGroupUsersRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaDemoteGroupUsersRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("user_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.UserIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FNakamaDemoteGroupUsersRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (UserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + return Json; +} + +FNakamaReadStorageObjectId FNakamaReadStorageObjectId::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaReadStorageObjectId Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("key"))) + { + Result.Key = Json->GetStringField(TEXT("key")); + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + return Result; +} + +TSharedPtr FNakamaReadStorageObjectId::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + if (!Key.IsEmpty()) + { + Json->SetStringField(TEXT("key"), Key); + } + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + return Json; +} + +FNakamaReadStorageObjectsRequest FNakamaReadStorageObjectsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaReadStorageObjectsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("object_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("object_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ObjectIds.Add(FNakamaReadStorageObjectId::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaReadStorageObjectsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (ObjectIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : ObjectIds) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("object_ids"), Array); + } + return Json; +} + +FNakamaRpc FNakamaRpc::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRpc Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("payload"))) + { + Result.Payload = Json->GetStringField(TEXT("payload")); + } + if (Json->HasField(TEXT("http_key"))) + { + Result.HttpKey = Json->GetStringField(TEXT("http_key")); + } + return Result; +} + +TSharedPtr FNakamaRpc::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Payload.IsEmpty()) + { + Json->SetStringField(TEXT("payload"), Payload); + } + if (!HttpKey.IsEmpty()) + { + Json->SetStringField(TEXT("http_key"), HttpKey); + } + return Json; +} + +FNakamaSession FNakamaSession::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaSession Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("created"))) + { + Result.Created = Json->GetBoolField(TEXT("created")); + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("refresh_token"))) + { + Result.RefreshToken = Json->GetStringField(TEXT("refresh_token")); + } + Result.ParseTokens(); + return Result; +} + +TSharedPtr FNakamaSession::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetBoolField(TEXT("created"), Created); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Json->SetStringField(TEXT("refresh_token"), RefreshToken); + } + return Json; +} + +FNakamaStorageObject FNakamaStorageObject::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaStorageObject Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("key"))) + { + Result.Key = Json->GetStringField(TEXT("key")); + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("version"))) + { + Result.Version = Json->GetStringField(TEXT("version")); + } + if (Json->HasField(TEXT("permission_read"))) + { + Result.PermissionRead = Json->GetIntegerField(TEXT("permission_read")); + } + if (Json->HasField(TEXT("permission_write"))) + { + Result.PermissionWrite = Json->GetIntegerField(TEXT("permission_write")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + return Result; +} + +TSharedPtr FNakamaStorageObject::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + if (!Key.IsEmpty()) + { + Json->SetStringField(TEXT("key"), Key); + } + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + if (!Version.IsEmpty()) + { + Json->SetStringField(TEXT("version"), Version); + } + Json->SetNumberField(TEXT("permission_read"), PermissionRead); + Json->SetNumberField(TEXT("permission_write"), PermissionWrite); + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + return Json; +} + +FNakamaStorageObjectAck FNakamaStorageObjectAck::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaStorageObjectAck Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("key"))) + { + Result.Key = Json->GetStringField(TEXT("key")); + } + if (Json->HasField(TEXT("version"))) + { + Result.Version = Json->GetStringField(TEXT("version")); + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + return Result; +} + +TSharedPtr FNakamaStorageObjectAck::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + if (!Key.IsEmpty()) + { + Json->SetStringField(TEXT("key"), Key); + } + if (!Version.IsEmpty()) + { + Json->SetStringField(TEXT("version"), Version); + } + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + return Json; +} + +FNakamaStorageObjectAcks FNakamaStorageObjectAcks::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaStorageObjectAcks Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("acks"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("acks"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Acks.Add(FNakamaStorageObjectAck::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaStorageObjectAcks::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Acks.Num() > 0) + { + TArray> Array; + for (const auto& Item : Acks) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("acks"), Array); + } + return Json; +} + +FNakamaStorageObjects FNakamaStorageObjects::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaStorageObjects Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("objects"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("objects"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Objects.Add(FNakamaStorageObject::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaStorageObjects::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Objects.Num() > 0) + { + TArray> Array; + for (const auto& Item : Objects) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("objects"), Array); + } + return Json; +} + +FNakamaStorageObjectList FNakamaStorageObjectList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaStorageObjectList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("objects"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("objects"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Objects.Add(FNakamaStorageObject::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaStorageObjectList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Objects.Num() > 0) + { + TArray> Array; + for (const auto& Item : Objects) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("objects"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaTournament FNakamaTournament::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaTournament Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("title"))) + { + Result.Title = Json->GetStringField(TEXT("title")); + } + if (Json->HasField(TEXT("description"))) + { + Result.Description = Json->GetStringField(TEXT("description")); + } + if (Json->HasField(TEXT("category"))) + { + Result.Category = static_cast(Json->GetNumberField(TEXT("category"))); + } + if (Json->HasField(TEXT("sort_order"))) + { + Result.SortOrder = static_cast(Json->GetNumberField(TEXT("sort_order"))); + } + if (Json->HasField(TEXT("size"))) + { + Result.Size = static_cast(Json->GetNumberField(TEXT("size"))); + } + if (Json->HasField(TEXT("max_size"))) + { + Result.MaxSize = static_cast(Json->GetNumberField(TEXT("max_size"))); + } + if (Json->HasField(TEXT("max_num_score"))) + { + Result.MaxNumScore = static_cast(Json->GetNumberField(TEXT("max_num_score"))); + } + if (Json->HasField(TEXT("can_enter"))) + { + Result.CanEnter = Json->GetBoolField(TEXT("can_enter")); + } + if (Json->HasField(TEXT("end_active"))) + { + Result.EndActive = static_cast(Json->GetNumberField(TEXT("end_active"))); + } + if (Json->HasField(TEXT("next_reset"))) + { + Result.NextReset = static_cast(Json->GetNumberField(TEXT("next_reset"))); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("start_time"))) + { + Result.StartTime = Json->GetStringField(TEXT("start_time")); + } + if (Json->HasField(TEXT("end_time"))) + { + Result.EndTime = Json->GetStringField(TEXT("end_time")); + } + if (Json->HasField(TEXT("duration"))) + { + Result.Duration = static_cast(Json->GetNumberField(TEXT("duration"))); + } + if (Json->HasField(TEXT("start_active"))) + { + Result.StartActive = static_cast(Json->GetNumberField(TEXT("start_active"))); + } + if (Json->HasField(TEXT("prev_reset"))) + { + Result.PrevReset = static_cast(Json->GetNumberField(TEXT("prev_reset"))); + } + if (Json->HasField(TEXT("operator"))) + { + Result.Operator = static_cast(Json->GetIntegerField(TEXT("operator"))); + } + if (Json->HasField(TEXT("authoritative"))) + { + Result.Authoritative = Json->GetBoolField(TEXT("authoritative")); + } + if (Json->HasField(TEXT("join_required"))) + { + Result.JoinRequired = Json->GetBoolField(TEXT("join_required")); + } + return Result; +} + +TSharedPtr FNakamaTournament::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Title.IsEmpty()) + { + Json->SetStringField(TEXT("title"), Title); + } + if (!Description.IsEmpty()) + { + Json->SetStringField(TEXT("description"), Description); + } + Json->SetNumberField(TEXT("category"), Category); + Json->SetNumberField(TEXT("sort_order"), SortOrder); + Json->SetNumberField(TEXT("size"), Size); + Json->SetNumberField(TEXT("max_size"), MaxSize); + Json->SetNumberField(TEXT("max_num_score"), MaxNumScore); + Json->SetBoolField(TEXT("can_enter"), CanEnter); + Json->SetNumberField(TEXT("end_active"), EndActive); + Json->SetNumberField(TEXT("next_reset"), NextReset); + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!StartTime.IsEmpty()) + { + Json->SetStringField(TEXT("start_time"), StartTime); + } + if (!EndTime.IsEmpty()) + { + Json->SetStringField(TEXT("end_time"), EndTime); + } + Json->SetNumberField(TEXT("duration"), Duration); + Json->SetNumberField(TEXT("start_active"), StartActive); + Json->SetNumberField(TEXT("prev_reset"), PrevReset); + Json->SetNumberField(TEXT("operator"), static_cast(Operator)); + Json->SetBoolField(TEXT("authoritative"), Authoritative); + Json->SetBoolField(TEXT("join_required"), JoinRequired); + return Json; +} + +FNakamaTournamentList FNakamaTournamentList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaTournamentList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournaments"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("tournaments"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Tournaments.Add(FNakamaTournament::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaTournamentList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Tournaments.Num() > 0) + { + TArray> Array; + for (const auto& Item : Tournaments) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("tournaments"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaTournamentRecordList FNakamaTournamentRecordList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaTournamentRecordList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("records"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("records"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Records.Add(FNakamaLeaderboardRecord::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("owner_records"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("owner_records"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.OwnerRecords.Add(FNakamaLeaderboardRecord::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("next_cursor"))) + { + Result.NextCursor = Json->GetStringField(TEXT("next_cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + if (Json->HasField(TEXT("rank_count"))) + { + Result.RankCount = static_cast(Json->GetNumberField(TEXT("rank_count"))); + } + return Result; +} + +TSharedPtr FNakamaTournamentRecordList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Records.Num() > 0) + { + TArray> Array; + for (const auto& Item : Records) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("records"), Array); + } + if (OwnerRecords.Num() > 0) + { + TArray> Array; + for (const auto& Item : OwnerRecords) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("owner_records"), Array); + } + if (!NextCursor.IsEmpty()) + { + Json->SetStringField(TEXT("next_cursor"), NextCursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + Json->SetNumberField(TEXT("rank_count"), RankCount); + return Json; +} + +FNakamaUpdateAccountRequest FNakamaUpdateAccountRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUpdateAccountRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("username"))) + { + Result.Username = Json->GetStringField(TEXT("username")); + } + if (Json->HasField(TEXT("display_name"))) + { + Result.DisplayName = Json->GetStringField(TEXT("display_name")); + } + if (Json->HasField(TEXT("avatar_url"))) + { + Result.AvatarUrl = Json->GetStringField(TEXT("avatar_url")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("location"))) + { + Result.Location = Json->GetStringField(TEXT("location")); + } + if (Json->HasField(TEXT("timezone"))) + { + Result.Timezone = Json->GetStringField(TEXT("timezone")); + } + return Result; +} + +TSharedPtr FNakamaUpdateAccountRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Username.IsEmpty()) + { + Json->SetStringField(TEXT("username"), Username); + } + if (!DisplayName.IsEmpty()) + { + Json->SetStringField(TEXT("display_name"), DisplayName); + } + if (!AvatarUrl.IsEmpty()) + { + Json->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!Location.IsEmpty()) + { + Json->SetStringField(TEXT("location"), Location); + } + if (!Timezone.IsEmpty()) + { + Json->SetStringField(TEXT("timezone"), Timezone); + } + return Json; +} + +FNakamaUpdateGroupRequest FNakamaUpdateGroupRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUpdateGroupRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group_id"))) + { + Result.GroupId = Json->GetStringField(TEXT("group_id")); + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("description"))) + { + Result.Description = Json->GetStringField(TEXT("description")); + } + if (Json->HasField(TEXT("lang_tag"))) + { + Result.LangTag = Json->GetStringField(TEXT("lang_tag")); + } + if (Json->HasField(TEXT("avatar_url"))) + { + Result.AvatarUrl = Json->GetStringField(TEXT("avatar_url")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + return Result; +} + +TSharedPtr FNakamaUpdateGroupRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!GroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), GroupId); + } + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Json->SetStringField(TEXT("description"), Description); + } + if (!LangTag.IsEmpty()) + { + Json->SetStringField(TEXT("lang_tag"), LangTag); + } + if (!AvatarUrl.IsEmpty()) + { + Json->SetStringField(TEXT("avatar_url"), AvatarUrl); + } + Json->SetBoolField(TEXT("open"), Open); + return Json; +} + +FNakamaUserGroupList_UserGroup FNakamaUserGroupList_UserGroup::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUserGroupList_UserGroup Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("group"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("group"), NestedObj)) + { + Result.Group = FNakamaGroup::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("state"))) + { + Result.State = Json->GetIntegerField(TEXT("state")); + } + return Result; +} + +TSharedPtr FNakamaUserGroupList_UserGroup::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("group"), Group.ToJson()); + Json->SetNumberField(TEXT("state"), State); + return Json; +} + +FNakamaUserGroupList FNakamaUserGroupList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUserGroupList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_groups"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("user_groups"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.UserGroups.Add(FNakamaUserGroupList_UserGroup::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaUserGroupList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (UserGroups.Num() > 0) + { + TArray> Array; + for (const auto& Item : UserGroups) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("user_groups"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaUsers FNakamaUsers::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaUsers Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("users"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("users"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Users.Add(FNakamaUser::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaUsers::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Users.Num() > 0) + { + TArray> Array; + for (const auto& Item : Users) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("users"), Array); + } + return Json; +} + +FNakamaValidatePurchaseAppleRequest FNakamaValidatePurchaseAppleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatePurchaseAppleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("receipt"))) + { + Result.Receipt = Json->GetStringField(TEXT("receipt")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidatePurchaseAppleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Receipt.IsEmpty()) + { + Json->SetStringField(TEXT("receipt"), Receipt); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidateSubscriptionAppleRequest FNakamaValidateSubscriptionAppleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidateSubscriptionAppleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("receipt"))) + { + Result.Receipt = Json->GetStringField(TEXT("receipt")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidateSubscriptionAppleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Receipt.IsEmpty()) + { + Json->SetStringField(TEXT("receipt"), Receipt); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidatePurchaseGoogleRequest FNakamaValidatePurchaseGoogleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatePurchaseGoogleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("purchase"))) + { + Result.Purchase = Json->GetStringField(TEXT("purchase")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidatePurchaseGoogleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Purchase.IsEmpty()) + { + Json->SetStringField(TEXT("purchase"), Purchase); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidateSubscriptionGoogleRequest FNakamaValidateSubscriptionGoogleRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidateSubscriptionGoogleRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("receipt"))) + { + Result.Receipt = Json->GetStringField(TEXT("receipt")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidateSubscriptionGoogleRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Receipt.IsEmpty()) + { + Json->SetStringField(TEXT("receipt"), Receipt); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidatePurchaseHuaweiRequest FNakamaValidatePurchaseHuaweiRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatePurchaseHuaweiRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("purchase"))) + { + Result.Purchase = Json->GetStringField(TEXT("purchase")); + } + if (Json->HasField(TEXT("signature"))) + { + Result.Signature = Json->GetStringField(TEXT("signature")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidatePurchaseHuaweiRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Purchase.IsEmpty()) + { + Json->SetStringField(TEXT("purchase"), Purchase); + } + if (!Signature.IsEmpty()) + { + Json->SetStringField(TEXT("signature"), Signature); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidatePurchaseFacebookInstantRequest FNakamaValidatePurchaseFacebookInstantRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatePurchaseFacebookInstantRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("signed_request"))) + { + Result.SignedRequest = Json->GetStringField(TEXT("signed_request")); + } + if (Json->HasField(TEXT("persist"))) + { + Result.Persist = Json->GetBoolField(TEXT("persist")); + } + return Result; +} + +TSharedPtr FNakamaValidatePurchaseFacebookInstantRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!SignedRequest.IsEmpty()) + { + Json->SetStringField(TEXT("signed_request"), SignedRequest); + } + Json->SetBoolField(TEXT("persist"), Persist); + return Json; +} + +FNakamaValidatedPurchase FNakamaValidatedPurchase::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatedPurchase Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("product_id"))) + { + Result.ProductId = Json->GetStringField(TEXT("product_id")); + } + if (Json->HasField(TEXT("transaction_id"))) + { + Result.TransactionId = Json->GetStringField(TEXT("transaction_id")); + } + if (Json->HasField(TEXT("store"))) + { + Result.Store = static_cast(Json->GetIntegerField(TEXT("store"))); + } + if (Json->HasField(TEXT("purchase_time"))) + { + Result.PurchaseTime = Json->GetStringField(TEXT("purchase_time")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("refund_time"))) + { + Result.RefundTime = Json->GetStringField(TEXT("refund_time")); + } + if (Json->HasField(TEXT("provider_response"))) + { + Result.ProviderResponse = Json->GetStringField(TEXT("provider_response")); + } + if (Json->HasField(TEXT("environment"))) + { + Result.Environment = static_cast(Json->GetIntegerField(TEXT("environment"))); + } + if (Json->HasField(TEXT("seen_before"))) + { + Result.SeenBefore = Json->GetBoolField(TEXT("seen_before")); + } + return Result; +} + +TSharedPtr FNakamaValidatedPurchase::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!ProductId.IsEmpty()) + { + Json->SetStringField(TEXT("product_id"), ProductId); + } + if (!TransactionId.IsEmpty()) + { + Json->SetStringField(TEXT("transaction_id"), TransactionId); + } + Json->SetNumberField(TEXT("store"), static_cast(Store)); + if (!PurchaseTime.IsEmpty()) + { + Json->SetStringField(TEXT("purchase_time"), PurchaseTime); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + if (!RefundTime.IsEmpty()) + { + Json->SetStringField(TEXT("refund_time"), RefundTime); + } + if (!ProviderResponse.IsEmpty()) + { + Json->SetStringField(TEXT("provider_response"), ProviderResponse); + } + Json->SetNumberField(TEXT("environment"), static_cast(Environment)); + Json->SetBoolField(TEXT("seen_before"), SeenBefore); + return Json; +} + +FNakamaValidatePurchaseResponse FNakamaValidatePurchaseResponse::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatePurchaseResponse Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("validated_purchases"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("validated_purchases"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ValidatedPurchases.Add(FNakamaValidatedPurchase::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaValidatePurchaseResponse::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (ValidatedPurchases.Num() > 0) + { + TArray> Array; + for (const auto& Item : ValidatedPurchases) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("validated_purchases"), Array); + } + return Json; +} + +FNakamaValidatedSubscription FNakamaValidatedSubscription::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidatedSubscription Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("user_id"))) + { + Result.UserId = Json->GetStringField(TEXT("user_id")); + } + if (Json->HasField(TEXT("product_id"))) + { + Result.ProductId = Json->GetStringField(TEXT("product_id")); + } + if (Json->HasField(TEXT("original_transaction_id"))) + { + Result.OriginalTransactionId = Json->GetStringField(TEXT("original_transaction_id")); + } + if (Json->HasField(TEXT("store"))) + { + Result.Store = static_cast(Json->GetIntegerField(TEXT("store"))); + } + if (Json->HasField(TEXT("purchase_time"))) + { + Result.PurchaseTime = Json->GetStringField(TEXT("purchase_time")); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = Json->GetStringField(TEXT("create_time")); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = Json->GetStringField(TEXT("update_time")); + } + if (Json->HasField(TEXT("environment"))) + { + Result.Environment = static_cast(Json->GetIntegerField(TEXT("environment"))); + } + if (Json->HasField(TEXT("expiry_time"))) + { + Result.ExpiryTime = Json->GetStringField(TEXT("expiry_time")); + } + if (Json->HasField(TEXT("refund_time"))) + { + Result.RefundTime = Json->GetStringField(TEXT("refund_time")); + } + if (Json->HasField(TEXT("provider_response"))) + { + Result.ProviderResponse = Json->GetStringField(TEXT("provider_response")); + } + if (Json->HasField(TEXT("provider_notification"))) + { + Result.ProviderNotification = Json->GetStringField(TEXT("provider_notification")); + } + if (Json->HasField(TEXT("active"))) + { + Result.Active = Json->GetBoolField(TEXT("active")); + } + return Result; +} + +TSharedPtr FNakamaValidatedSubscription::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!UserId.IsEmpty()) + { + Json->SetStringField(TEXT("user_id"), UserId); + } + if (!ProductId.IsEmpty()) + { + Json->SetStringField(TEXT("product_id"), ProductId); + } + if (!OriginalTransactionId.IsEmpty()) + { + Json->SetStringField(TEXT("original_transaction_id"), OriginalTransactionId); + } + Json->SetNumberField(TEXT("store"), static_cast(Store)); + if (!PurchaseTime.IsEmpty()) + { + Json->SetStringField(TEXT("purchase_time"), PurchaseTime); + } + if (!CreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), CreateTime); + } + if (!UpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), UpdateTime); + } + Json->SetNumberField(TEXT("environment"), static_cast(Environment)); + if (!ExpiryTime.IsEmpty()) + { + Json->SetStringField(TEXT("expiry_time"), ExpiryTime); + } + if (!RefundTime.IsEmpty()) + { + Json->SetStringField(TEXT("refund_time"), RefundTime); + } + if (!ProviderResponse.IsEmpty()) + { + Json->SetStringField(TEXT("provider_response"), ProviderResponse); + } + if (!ProviderNotification.IsEmpty()) + { + Json->SetStringField(TEXT("provider_notification"), ProviderNotification); + } + Json->SetBoolField(TEXT("active"), Active); + return Json; +} + +FNakamaValidateSubscriptionResponse FNakamaValidateSubscriptionResponse::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaValidateSubscriptionResponse Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("validated_subscription"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("validated_subscription"), NestedObj)) + { + Result.ValidatedSubscription = FNakamaValidatedSubscription::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaValidateSubscriptionResponse::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("validated_subscription"), ValidatedSubscription.ToJson()); + return Json; +} + +FNakamaPurchaseList FNakamaPurchaseList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaPurchaseList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("validated_purchases"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("validated_purchases"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ValidatedPurchases.Add(FNakamaValidatedPurchase::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + return Result; +} + +TSharedPtr FNakamaPurchaseList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (ValidatedPurchases.Num() > 0) + { + TArray> Array; + for (const auto& Item : ValidatedPurchases) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("validated_purchases"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + return Json; +} + +FNakamaSubscriptionList FNakamaSubscriptionList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaSubscriptionList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("validated_subscriptions"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("validated_subscriptions"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ValidatedSubscriptions.Add(FNakamaValidatedSubscription::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + return Result; +} + +TSharedPtr FNakamaSubscriptionList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (ValidatedSubscriptions.Num() > 0) + { + TArray> Array; + for (const auto& Item : ValidatedSubscriptions) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("validated_subscriptions"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + return Json; +} + +FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("score"))) + { + Result.Score = static_cast(Json->GetNumberField(TEXT("score"))); + } + if (Json->HasField(TEXT("subscore"))) + { + Result.Subscore = static_cast(Json->GetNumberField(TEXT("subscore"))); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("operator"))) + { + Result.Operator = static_cast(Json->GetIntegerField(TEXT("operator"))); + } + return Result; +} + +TSharedPtr FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("score"), Score); + Json->SetNumberField(TEXT("subscore"), Subscore); + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + Json->SetNumberField(TEXT("operator"), static_cast(Operator)); + return Json; +} + +FNakamaWriteLeaderboardRecordRequest FNakamaWriteLeaderboardRecordRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteLeaderboardRecordRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("leaderboard_id"))) + { + Result.LeaderboardId = Json->GetStringField(TEXT("leaderboard_id")); + } + if (Json->HasField(TEXT("record"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("record"), NestedObj)) + { + Result.Record = FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaWriteLeaderboardRecordRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!LeaderboardId.IsEmpty()) + { + Json->SetStringField(TEXT("leaderboard_id"), LeaderboardId); + } + Json->SetObjectField(TEXT("record"), Record.ToJson()); + return Json; +} + +FNakamaWriteStorageObject FNakamaWriteStorageObject::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteStorageObject Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("collection"))) + { + Result.Collection = Json->GetStringField(TEXT("collection")); + } + if (Json->HasField(TEXT("key"))) + { + Result.Key = Json->GetStringField(TEXT("key")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("version"))) + { + Result.Version = Json->GetStringField(TEXT("version")); + } + if (Json->HasField(TEXT("permission_read"))) + { + Result.PermissionRead = Json->GetIntegerField(TEXT("permission_read")); + } + if (Json->HasField(TEXT("permission_write"))) + { + Result.PermissionWrite = Json->GetIntegerField(TEXT("permission_write")); + } + return Result; +} + +TSharedPtr FNakamaWriteStorageObject::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Collection.IsEmpty()) + { + Json->SetStringField(TEXT("collection"), Collection); + } + if (!Key.IsEmpty()) + { + Json->SetStringField(TEXT("key"), Key); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + if (!Version.IsEmpty()) + { + Json->SetStringField(TEXT("version"), Version); + } + Json->SetNumberField(TEXT("permission_read"), PermissionRead); + Json->SetNumberField(TEXT("permission_write"), PermissionWrite); + return Json; +} + +FNakamaWriteStorageObjectsRequest FNakamaWriteStorageObjectsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteStorageObjectsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("objects"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("objects"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Objects.Add(FNakamaWriteStorageObject::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FNakamaWriteStorageObjectsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Objects.Num() > 0) + { + TArray> Array; + for (const auto& Item : Objects) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("objects"), Array); + } + return Json; +} + +FNakamaWriteTournamentRecordRequest_TournamentRecordWrite FNakamaWriteTournamentRecordRequest_TournamentRecordWrite::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("score"))) + { + Result.Score = static_cast(Json->GetNumberField(TEXT("score"))); + } + if (Json->HasField(TEXT("subscore"))) + { + Result.Subscore = static_cast(Json->GetNumberField(TEXT("subscore"))); + } + if (Json->HasField(TEXT("metadata"))) + { + Result.Metadata = Json->GetStringField(TEXT("metadata")); + } + if (Json->HasField(TEXT("operator"))) + { + Result.Operator = static_cast(Json->GetIntegerField(TEXT("operator"))); + } + return Result; +} + +TSharedPtr FNakamaWriteTournamentRecordRequest_TournamentRecordWrite::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("score"), Score); + Json->SetNumberField(TEXT("subscore"), Subscore); + if (!Metadata.IsEmpty()) + { + Json->SetStringField(TEXT("metadata"), Metadata); + } + Json->SetNumberField(TEXT("operator"), static_cast(Operator)); + return Json; +} + +FNakamaWriteTournamentRecordRequest FNakamaWriteTournamentRecordRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaWriteTournamentRecordRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("tournament_id"))) + { + Result.TournamentId = Json->GetStringField(TEXT("tournament_id")); + } + if (Json->HasField(TEXT("record"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("record"), NestedObj)) + { + Result.Record = FNakamaWriteTournamentRecordRequest_TournamentRecordWrite::FromJson(*NestedObj); + } + } + return Result; +} + +TSharedPtr FNakamaWriteTournamentRecordRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!TournamentId.IsEmpty()) + { + Json->SetStringField(TEXT("tournament_id"), TournamentId); + } + Json->SetObjectField(TEXT("record"), Record.ToJson()); + return Json; +} + +FNakamaListPartiesRequest FNakamaListPartiesRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaListPartiesRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("query"))) + { + Result.Query = Json->GetStringField(TEXT("query")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaListPartiesRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetBoolField(TEXT("open"), Open); + if (!Query.IsEmpty()) + { + Json->SetStringField(TEXT("query"), Query); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +FNakamaParty FNakamaParty::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaParty Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("party_id"))) + { + Result.PartyId = Json->GetStringField(TEXT("party_id")); + } + if (Json->HasField(TEXT("open"))) + { + Result.Open = Json->GetBoolField(TEXT("open")); + } + if (Json->HasField(TEXT("hidden"))) + { + Result.Hidden = Json->GetBoolField(TEXT("hidden")); + } + if (Json->HasField(TEXT("max_size"))) + { + Result.MaxSize = Json->GetIntegerField(TEXT("max_size")); + } + if (Json->HasField(TEXT("label"))) + { + Result.Label = Json->GetStringField(TEXT("label")); + } + return Result; +} + +TSharedPtr FNakamaParty::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!PartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), PartyId); + } + Json->SetBoolField(TEXT("open"), Open); + Json->SetBoolField(TEXT("hidden"), Hidden); + Json->SetNumberField(TEXT("max_size"), MaxSize); + if (!Label.IsEmpty()) + { + Json->SetStringField(TEXT("label"), Label); + } + return Json; +} + +FNakamaPartyList FNakamaPartyList::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaPartyList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("parties"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("parties"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Parties.Add(FNakamaParty::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + return Result; +} + +TSharedPtr FNakamaPartyList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Parties.Num() > 0) + { + TArray> Array; + for (const auto& Item : Parties) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("parties"), Array); + } + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + return Json; +} + +// --- FNakamaClientConfig --- + +FString FNakamaClientConfig::GetBaseUrl() const noexcept +{ + const FString Scheme = bUseSSL ? TEXT("https") : TEXT("http"); + return FString::Printf(TEXT("%s://%s:%d"), *Scheme, *Host, Port); +} + diff --git a/Nakama/Source/NakamaApi/Public/AsyncFuture.h b/Nakama/Source/NakamaApi/Public/AsyncFuture.h new file mode 100644 index 000000000..2e7976f96 --- /dev/null +++ b/Nakama/Source/NakamaApi/Public/AsyncFuture.h @@ -0,0 +1,198 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "Tasks/Task.h" +#include "Async/TaskGraphInterfaces.h" +#include + +// Forward declaration +template struct TAsyncFuture; + +/** Type trait: is T a TAsyncFuture? */ +template struct TIsTAsyncFuture : std::false_type {}; +template struct TIsTAsyncFuture> : std::true_type {}; + +/** + * Chainable, game-thread-safe future for async operations. + * + * Parameterized by a concrete result type (e.g. FNakamaSessionResult or + * FSatoriSessionResult). Every user-visible callback is dispatched to the + * game thread via AsyncTask(ENamedThreads::GameThread, ...) so that callers + * can safely touch UObject*, fire delegates, or update UI without additional + * marshalling. + * + * Three .Next() overloads are provided: + * + * 1. Chaining with auto-propagation: + * Next(callback(const ValueType&) -> TAsyncFuture) + * Available only when ResultT defines ::ValueType. + * On error the error is forwarded to the outer future and the callback is + * skipped entirely (error propagation runs on the background thread because + * no user code is involved). + * + * 2. Chaining without auto-propagation: + * Next(callback(ResultT) -> TAsyncFuture) + * Available for all ResultT, including types without ::ValueType (e.g. + * WebSocket envelope types). The callback receives the full result and is + * responsible for inspecting bIsError and deciding what to return. + * + * 3. Terminal: + * Next(callback(ResultT) -> void) + * Called unconditionally when the future resolves. The caller inspects + * the result for errors inside the callback. + */ +template +struct TAsyncFuture +{ + using WrappedResultType = ResultT; + + struct FState + { + ResultT Result{}; + UE::Tasks::FTaskEvent Event{ UE_SOURCE_LOCATION }; + void Resolve(ResultT&& InResult) + { + Result = MoveTemp(InResult); + Event.Trigger(); + } + }; + + TSharedPtr State; + + TAsyncFuture() = default; + explicit TAsyncFuture(TSharedPtr InState) noexcept + : State(MoveTemp(InState)) {} + TAsyncFuture(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture& operator=(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture(const TAsyncFuture&) = delete; + TAsyncFuture& operator=(const TAsyncFuture&) = delete; + + /** + * Overload 1 — Chaining with auto-propagation. + * callback(const ValueType&) -> TAsyncFuture + * Only enabled when ResultT has ::ValueType. + * On error, propagates to OtherResult without calling the callback. + */ + template, const VT&>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + if (CapturedState->Result.bIsError) + { + // Error propagation: no user code involved, safe on any thread. + OuterState->Resolve(InnerResultT{{}, CapturedState->Result.Error, true}); + return; + } + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(CapturedState->Result.Value); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** + * Overload 2 — Chaining, user handles errors. + * callback(ResultT) -> TAsyncFuture + * Available for all ResultT, including WebSocket types without ::ValueType. + * The callback receives the full result and is responsible for error handling. + */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(MoveTemp(CapturedState->Result)); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** Overload 3 — Terminal. callback(ResultT) -> void */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + void Next(Func&& Callback) && noexcept + { + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState]() mutable + { + Cb(MoveTemp(CapturedState->Result)); + }); + }, + CapturedState->Event); + State.Reset(); + } +}; + +/** + * Create a pre-resolved TAsyncFuture. + * Useful for returning an immediate result (e.g. a local error) from a + * chaining callback without going through the network stack. + */ +template +TAsyncFuture MakeCompletedAsyncFuture(ResultT Value) +{ + auto State = MakeShared::FState>(); + State->Resolve(MoveTemp(Value)); + return TAsyncFuture(State); +} diff --git a/Nakama/Source/NakamaApi/Public/NakamaApi.h b/Nakama/Source/NakamaApi/Public/NakamaApi.h new file mode 100644 index 000000000..6445054b5 --- /dev/null +++ b/Nakama/Source/NakamaApi/Public/NakamaApi.h @@ -0,0 +1,1863 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" +#include "NakamaTypes.h" + +NAKAMAAPI_API DECLARE_LOG_CATEGORY_EXTERN(LogNakama, Log, All); + +/** Low-level Nakama API: data types + free functions for HTTP RPCs (callback-based). */ +namespace NakamaApi +{ + + /** Add friends by ID or username to a user's account. */ + NAKAMAAPI_API void AddFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add friends by ID or username to a user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void AddFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + FString Metadata, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add users to a group. */ + NAKAMAAPI_API void AddGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add users to a group. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void AddGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */ + NAKAMAAPI_API void SessionRefresh( + const FNakamaClientConfig& Config, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ + NAKAMAAPI_API void SessionLogout( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void SessionLogout( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with an Apple ID against the server. */ + NAKAMAAPI_API void AuthenticateApple( + const FNakamaClientConfig& Config, + FNakamaAccountApple Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a custom id against the server. */ + NAKAMAAPI_API void AuthenticateCustom( + const FNakamaClientConfig& Config, + FNakamaAccountCustom Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a device id against the server. */ + NAKAMAAPI_API void AuthenticateDevice( + const FNakamaClientConfig& Config, + FNakamaAccountDevice Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with an email+password against the server. */ + NAKAMAAPI_API void AuthenticateEmail( + const FNakamaClientConfig& Config, + FNakamaAccountEmail Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a Facebook OAuth token against the server. */ + NAKAMAAPI_API void AuthenticateFacebook( + const FNakamaClientConfig& Config, + FNakamaAccountFacebook Account, + bool Create, + FString Username, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with a Facebook Instant Game token against the server. */ + NAKAMAAPI_API void AuthenticateFacebookInstantGame( + const FNakamaClientConfig& Config, + FNakamaAccountFacebookInstantGame Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Apple's GameCenter against the server. */ + NAKAMAAPI_API void AuthenticateGameCenter( + const FNakamaClientConfig& Config, + FNakamaAccountGameCenter Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Google against the server. */ + NAKAMAAPI_API void AuthenticateGoogle( + const FNakamaClientConfig& Config, + FNakamaAccountGoogle Account, + bool Create, + FString Username, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Authenticate a user with Steam against the server. */ + NAKAMAAPI_API void AuthenticateSteam( + const FNakamaClientConfig& Config, + FNakamaAccountSteam Account, + bool Create, + FString Username, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Ban a set of users from a group. */ + NAKAMAAPI_API void BanGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Ban a set of users from a group. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void BanGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Block one or more users by ID or username. */ + NAKAMAAPI_API void BlockFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Block one or more users by ID or username. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void BlockFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Create a new group with the current user as the owner. */ + NAKAMAAPI_API void CreateGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Create a new group with the current user as the owner. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void CreateGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the current user's account. */ + NAKAMAAPI_API void DeleteAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more users by ID or username. */ + NAKAMAAPI_API void DeleteFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more users by ID or username. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a group by ID. */ + NAKAMAAPI_API void DeleteGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a group by ID. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a leaderboard record. */ + NAKAMAAPI_API void DeleteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a leaderboard record. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more notifications for the current user. */ + NAKAMAAPI_API void DeleteNotifications( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more notifications for the current user. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteNotifications( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a tournament record. */ + NAKAMAAPI_API void DeleteTournamentRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete a tournament record. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteTournamentRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more objects by ID or username. */ + NAKAMAAPI_API void DeleteStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete one or more objects by ID or username. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DeleteStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Submit an event for processing in the server's registered runtime custom events handler. */ + NAKAMAAPI_API void Event( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Submit an event for processing in the server's registered runtime custom events handler. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void Event( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch the current user's account. */ + NAKAMAAPI_API void GetAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void GetAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch zero or more users by ID and/or username. */ + NAKAMAAPI_API void GetUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch zero or more users by ID and/or username. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void GetUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get subscription by product id. */ + NAKAMAAPI_API void GetSubscription( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString ProductId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get subscription by product id. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void GetSubscription( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString ProductId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get matchmaker stats. */ + NAKAMAAPI_API void GetMatchmakerStats( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get matchmaker stats. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void GetMatchmakerStats( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. */ + NAKAMAAPI_API void Healthcheck( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void Healthcheck( + const FNakamaClientConfig& Config, + const FString& HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Facebook friends and add them to a user's account. */ + NAKAMAAPI_API void ImportFacebookFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountFacebook Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Facebook friends and add them to a user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ImportFacebookFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountFacebook Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Steam friends and add them to a user's account. */ + NAKAMAAPI_API void ImportSteamFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountSteam Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Import Steam friends and add them to a user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ImportSteamFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountSteam Account, + bool Reset, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Immediately join an open group, or request to join a closed one. */ + NAKAMAAPI_API void JoinGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Immediately join an open group, or request to join a closed one. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void JoinGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Attempt to join an open and running tournament. */ + NAKAMAAPI_API void JoinTournament( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Attempt to join an open and running tournament. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void JoinTournament( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Kick a set of users from a group. */ + NAKAMAAPI_API void KickGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Kick a set of users from a group. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void KickGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Leave a group the user is a member of. */ + NAKAMAAPI_API void LeaveGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Leave a group the user is a member of. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LeaveGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an Apple ID to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an Apple ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a custom ID to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkCustom( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a custom ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkCustom( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a device ID to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkDevice( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add a device ID to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkDevice( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an email+password to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkEmail( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add an email+password to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkEmail( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkFacebook( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountFacebook Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkFacebook( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountFacebook Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook Instant Game to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Facebook Instant Game to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Apple's GameCenter to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkGameCenter( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Apple's GameCenter to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkGameCenter( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Google to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Google to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Steam to the social profiles on the current user's account. */ + NAKAMAAPI_API void LinkSteam( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FNakamaAccountSteam Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Add Steam to the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void LinkSteam( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FNakamaAccountSteam Account, + bool Sync, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List a channel's message history. */ + NAKAMAAPI_API void ListChannelMessages( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List a channel's message history. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListChannelMessages( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all friends for the current user. */ + NAKAMAAPI_API void ListFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all friends for the current user. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List friends of friends for the current user. */ + NAKAMAAPI_API void ListFriendsOfFriends( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List friends of friends for the current user. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListFriendsOfFriends( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups based on given filters. */ + NAKAMAAPI_API void ListGroups( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups based on given filters. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListGroups( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all users that are part of a group. */ + NAKAMAAPI_API void ListGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all users that are part of a group. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records. */ + NAKAMAAPI_API void ListLeaderboardRecords( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListLeaderboardRecords( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records around the target ownerId. */ + NAKAMAAPI_API void ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List leaderboard records around the target ownerId. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListLeaderboardRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List running matches and optionally filter by matching criteria. */ + NAKAMAAPI_API void ListMatches( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List running matches and optionally filter by matching criteria. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListMatches( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List parties and optionally filter by matching criteria. */ + NAKAMAAPI_API void ListParties( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List parties and optionally filter by matching criteria. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListParties( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + bool Open, + FString Query, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch list of notifications. */ + NAKAMAAPI_API void ListNotifications( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Fetch list of notifications. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListNotifications( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString CacheableCursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List publicly readable storage objects in a given collection. */ + NAKAMAAPI_API void ListStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List publicly readable storage objects in a given collection. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List user's subscriptions. */ + NAKAMAAPI_API void ListSubscriptions( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List user's subscriptions. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListSubscriptions( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List current or upcoming tournaments. */ + NAKAMAAPI_API void ListTournaments( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List current or upcoming tournaments. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListTournaments( + const FNakamaClientConfig& Config, + const FString& HttpKey, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records. */ + NAKAMAAPI_API void ListTournamentRecords( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListTournamentRecords( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records for a given owner. */ + NAKAMAAPI_API void ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List tournament records for a given owner. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListTournamentRecordsAroundOwner( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups the current user belongs to. */ + NAKAMAAPI_API void ListUserGroups( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List groups the current user belongs to. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ListUserGroups( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString UserId, + int32 Limit, + int32 State, + FString Cursor, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Promote a set of users in a group to the next role up. */ + NAKAMAAPI_API void PromoteGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Promote a set of users in a group to the next role up. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void PromoteGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Demote a set of users in a group to the next role down. */ + NAKAMAAPI_API void DemoteGroupUsers( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Demote a set of users in a group to the next role down. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void DemoteGroupUsers( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + const TArray& UserIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get storage objects. */ + NAKAMAAPI_API void ReadStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get storage objects. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ReadStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& ObjectIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Execute a Lua function on the server. */ + NAKAMAAPI_API void RpcFunc( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + TSharedPtr Payload, + FString HttpKey, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Execute a Lua function on the server. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void RpcFunc( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + TSharedPtr Payload, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the Apple ID from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the Apple ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the custom ID from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkCustom( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the custom ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkCustom( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the device ID from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkDevice( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the device ID from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkDevice( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Id, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the email+password from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkEmail( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove the email+password from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkEmail( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Email, + FString Password, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkFacebook( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkFacebook( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook Instant Game profile from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Facebook Instant Game profile from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkFacebookInstantGame( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedPlayerInfo, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Apple's GameCenter from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkGameCenter( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Apple's GameCenter from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkGameCenter( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Google from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Google from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Steam from the social profiles on the current user's account. */ + NAKAMAAPI_API void UnlinkSteam( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Remove Steam from the social profiles on the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UnlinkSteam( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Token, + const TMap& Vars, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in the current user's account. */ + NAKAMAAPI_API void UpdateAccount( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in the current user's account. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UpdateAccount( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in a given group. */ + NAKAMAAPI_API void UpdateGroup( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update fields in a given group. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void UpdateGroup( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple IAP Receipt */ + NAKAMAAPI_API void ValidatePurchaseApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple IAP Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidatePurchaseApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple Subscription Receipt */ + NAKAMAAPI_API void ValidateSubscriptionApple( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Apple Subscription Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidateSubscriptionApple( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google IAP Receipt */ + NAKAMAAPI_API void ValidatePurchaseGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Purchase, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google IAP Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidatePurchaseGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Purchase, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google Subscription Receipt */ + NAKAMAAPI_API void ValidateSubscriptionGoogle( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Google Subscription Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidateSubscriptionGoogle( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Receipt, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Huawei IAP Receipt */ + NAKAMAAPI_API void ValidatePurchaseHuawei( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate Huawei IAP Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidatePurchaseHuawei( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString Purchase, + FString Signature, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate FB Instant IAP Receipt */ + NAKAMAAPI_API void ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Validate FB Instant IAP Receipt (Server-to-server with HTTP key) */ + NAKAMAAPI_API void ValidatePurchaseFacebookInstant( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString SignedRequest, + bool Persist, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a leaderboard. */ + NAKAMAAPI_API void WriteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString LeaderboardId, + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a leaderboard. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void WriteLeaderboardRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString LeaderboardId, + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write objects into the storage engine. */ + NAKAMAAPI_API void WriteStorageObjects( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + const TArray& Objects, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write objects into the storage engine. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void WriteStorageObjects( + const FNakamaClientConfig& Config, + const FString& HttpKey, + const TArray& Objects, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a tournament. */ + NAKAMAAPI_API void WriteTournamentRecord( + const FNakamaClientConfig& Config, + const FNakamaSession& Session, + FString TournamentId, + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Write a record to a tournament. (Server-to-server with HTTP key) */ + NAKAMAAPI_API void WriteTournamentRecord( + const FNakamaClientConfig& Config, + const FString& HttpKey, + FString TournamentId, + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +} diff --git a/Nakama/Source/NakamaApi/Public/NakamaHttpHelper.h b/Nakama/Source/NakamaApi/Public/NakamaHttpHelper.h new file mode 100644 index 000000000..7ad33ca22 --- /dev/null +++ b/Nakama/Source/NakamaApi/Public/NakamaHttpHelper.h @@ -0,0 +1,238 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Dom/JsonObject.h" +#include "HttpModule.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Misc/Base64.h" +#include "Misc/DateTime.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/MemoryWriter.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "NakamaTypes.h" + +/** + * Internal HTTP helpers for NakamaApi. + * All functions are typed against FNakamaClientConfig and ENakamaRequestAuth. + * + * Include this header in generated .cpp files only — not in public API headers. + */ +namespace NakamaHttpInternal +{ + +inline FString SerializeJsonToString(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(1024); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + return FString(Converter.Length(), Converter.Get()); +} + +inline FString SerializeJsonEscaped(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(2048); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + FString Condensed(Converter.Length(), Converter.Get()); + FString Escaped = Condensed.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")); + return TEXT("\"") + Escaped + TEXT("\""); +} + +inline void DoHttpRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ENakamaRequestAuth AuthType, + const FString& TokenString, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + const FString Url = Config.GetBaseUrl() + Endpoint; + + TSharedRef Request = FHttpModule::Get().CreateRequest(); + Request->SetURL(Url); + Request->SetVerb(Method); + Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + Request->SetHeader(TEXT("Accept"), TEXT("application/json")); + + switch (AuthType) + { + case ENakamaRequestAuth::Basic: + { + const FString Auth = FString::Printf(TEXT("%s:"), *Config.ServerKey); + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Basic %s"), *FBase64::Encode(Auth))); + } + break; + case ENakamaRequestAuth::Bearer: + if (!TokenString.IsEmpty()) + { + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Bearer %s"), *TokenString)); + } + break; + case ENakamaRequestAuth::HttpKey: + if (!TokenString.IsEmpty()) + { + const FString Auth = FString::Printf(TEXT("%s:"), *TokenString); + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Basic %s"), *FBase64::Encode(Auth))); + } + break; + case ENakamaRequestAuth::None: + default: + break; + } + + if (!BodyString.IsEmpty() && Method != TEXT("GET")) + { + Request->SetContentAsString(BodyString); + } + + Request->SetTimeout(Timeout); + + Request->OnProcessRequestComplete().BindLambda( + [OnSuccess, OnError, CancellationToken](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess) + { + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Request cancelled"), -1)); + } + return; + } + + if (!bSuccess || !Res.IsValid()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Connection failed"), 0)); + } + return; + } + + const int32 Code = Res->GetResponseCode(); + const FString Content = Res->GetContentAsString(); + + if (Code < 200 || Code >= 300) + { + FString ErrorMsg = FString::Printf(TEXT("HTTP %d"), Code); + int32 ErrorCode = Code; + TSharedPtr Json; + if (FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) && Json.IsValid()) + { + if (Json->HasField(TEXT("message"))) + { + ErrorMsg = Json->GetStringField(TEXT("message")); + } + if (Json->HasField(TEXT("code"))) + { + ErrorCode = static_cast(Json->GetNumberField(TEXT("code"))); + } + } + if (OnError) + { + OnError(FNakamaError(ErrorMsg, ErrorCode)); + } + return; + } + + TSharedPtr Json; + if (!Content.IsEmpty()) + { + if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) || !Json.IsValid()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Invalid JSON response"), 500)); + } + return; + } + } + + if (OnSuccess) + { + OnSuccess(Json); + } + }); + + Request->ProcessRequest(); +} + +inline void SendRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ENakamaRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Request cancelled"), -1)); + } + return; + } + + DoHttpRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +inline void MakeRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const TSharedPtr& Body, + ENakamaRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString BodyString; + if (Body.IsValid() && Method != TEXT("GET")) + { + BodyString = SerializeJsonToString(Body); + } + SendRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +} // namespace NakamaHttpInternal diff --git a/Nakama/Source/NakamaApi/Public/NakamaRtTypes.h b/Nakama/Source/NakamaApi/Public/NakamaRtTypes.h new file mode 100644 index 000000000..e1012de7b --- /dev/null +++ b/Nakama/Source/NakamaApi/Public/NakamaRtTypes.h @@ -0,0 +1,1057 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" + +#include "NakamaRtTypes.generated.h" + +// Forward declarations +struct FNakamaRtEnvelope; +struct FNakamaRtUserPresence; +struct FNakamaRtChannel; +struct FNakamaRtChannelJoin; +struct FNakamaRtChannelLeave; +struct FNakamaRtChannelMessageAck; +struct FNakamaRtChannelMessageSend; +struct FNakamaRtChannelMessageUpdate; +struct FNakamaRtChannelMessageRemove; +struct FNakamaRtChannelPresenceEvent; +struct FNakamaRtError; +struct FNakamaRtMatch; +struct FNakamaRtMatchCreate; +struct FNakamaRtMatchData; +struct FNakamaRtMatchDataSend; +struct FNakamaRtMatchJoin; +struct FNakamaRtMatchLeave; +struct FNakamaRtMatchPresenceEvent; +struct FNakamaRtMatchmakerAdd; +struct FNakamaRtMatchmakerMatched_MatchmakerUser; +struct FNakamaRtMatchmakerMatched; +struct FNakamaRtMatchmakerRemove; +struct FNakamaRtMatchmakerTicket; +struct FNakamaRtNotifications; +struct FNakamaRtParty; +struct FNakamaRtPartyCreate; +struct FNakamaRtPartyUpdate; +struct FNakamaRtPartyJoin; +struct FNakamaRtPartyLeave; +struct FNakamaRtPartyPromote; +struct FNakamaRtPartyLeader; +struct FNakamaRtPartyAccept; +struct FNakamaRtPartyRemove; +struct FNakamaRtPartyClose; +struct FNakamaRtPartyJoinRequestList; +struct FNakamaRtPartyJoinRequest; +struct FNakamaRtPartyMatchmakerAdd; +struct FNakamaRtPartyMatchmakerRemove; +struct FNakamaRtPartyMatchmakerTicket; +struct FNakamaRtPartyData; +struct FNakamaRtPartyDataSend; +struct FNakamaRtPartyPresenceEvent; +struct FNakamaRtPing; +struct FNakamaRtPong; +struct FNakamaRtStatus; +struct FNakamaRtStatusFollow; +struct FNakamaRtStatusPresenceEvent; +struct FNakamaRtStatusUnfollow; +struct FNakamaRtStatusUpdate; +struct FNakamaRtStream; +struct FNakamaRtStreamData; +struct FNakamaRtStreamPresenceEvent; + +/** An envelope for a realtime message. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtEnvelope +{ + GENERATED_BODY() + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cid; + + static FNakamaRtEnvelope FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A user session associated to a stream, usually through a list operation or a join/leave event. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtUserPresence +{ + GENERATED_BODY() + /** The user this presence belongs to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** A unique session ID identifying the particular connection, because the user may have many. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SessionId; + /** The username for display purposes. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** Whether this presence generates persistent data/messages, if applicable for the stream type. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persistence = false; + /** A user-set status message for this stream, if applicable. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Status; + + static FNakamaRtUserPresence FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A realtime chat channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannel +{ + GENERATED_BODY() + /** The ID of the channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** The users currently in the channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + /** A reference to the current user's presence in the channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Self; + /** The name of the chat room, or an empty string if this message was not sent through a chat room. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RoomName; + /** The ID of the group, or an empty string if this message was not sent through a group channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The ID of the first DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdOne; + /** The ID of the second DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdTwo; + + static FNakamaRtChannel FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Join operation for a realtime chat channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelJoin +{ + GENERATED_BODY() + /** The user ID to DM with, group ID to chat with, or room channel name to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Target; + /** The type of the chat channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Type = 0; + /** Whether messages sent on this channel should be persistent. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persistence = false; + /** Whether the user should appear in the channel's presence list and events. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Hidden = false; + + static FNakamaRtChannelJoin FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Leave a realtime channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelLeave +{ + GENERATED_BODY() + /** The ID of the channel to leave. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + + static FNakamaRtChannelLeave FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A receipt reply from a channel message send operation. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelMessageAck +{ + GENERATED_BODY() + /** The channel the message was sent to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** The unique ID assigned to the message. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MessageId; + /** The code representing a message type or category. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; + /** Username of the message sender. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** True if the message was persisted to the channel's history, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persistent = false; + /** The name of the chat room, or an empty string if this message was not sent through a chat room. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RoomName; + /** The ID of the group, or an empty string if this message was not sent through a group channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The ID of the first DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdOne; + /** The ID of the second DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdTwo; + + static FNakamaRtChannelMessageAck FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a message to a realtime channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelMessageSend +{ + GENERATED_BODY() + /** The channel to sent to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** Message content. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Content; + + static FNakamaRtChannelMessageSend FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Update a message previously sent to a realtime channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelMessageUpdate +{ + GENERATED_BODY() + /** The channel the message was sent to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** The ID assigned to the message to update. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MessageId; + /** New message content. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Content; + + static FNakamaRtChannelMessageUpdate FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Remove a message previously sent to a realtime channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelMessageRemove +{ + GENERATED_BODY() + /** The channel the message was sent to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** The ID assigned to the message to update. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MessageId; + + static FNakamaRtChannelMessageRemove FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A set of joins and leaves on a particular channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtChannelPresenceEvent +{ + GENERATED_BODY() + /** The channel identifier this event is for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** Presences joining the channel as part of this event, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Joins; + /** Presences leaving the channel as part of this event, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaves; + /** The name of the chat room, or an empty string if this message was not sent through a chat room. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RoomName; + /** The ID of the group, or an empty string if this message was not sent through a group channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The ID of the first DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdOne; + /** The ID of the second DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdTwo; + + static FNakamaRtChannelPresenceEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A logical error which may occur on the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtError +{ + GENERATED_BODY() + /** The error code which should be one of "Error.Code" enums. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; + /** A message in English to help developers debug the response. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Message; + /** Additional error details which may be different for each response. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Context; + + static FNakamaRtError FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatch +{ + GENERATED_BODY() + /** The match unique ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + /** True if it's an server-managed authoritative match, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Authoritative = false; + /** Match label, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + /** The number of users currently in the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Size = 0; + /** The users currently in the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + /** A reference to the current user's presence in the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Self; + + static FNakamaRtMatch FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Create a new realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchCreate +{ + GENERATED_BODY() + /** Optional name to use when creating the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + + static FNakamaRtMatchCreate FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Realtime match data received from the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchData +{ + GENERATED_BODY() + /** The match unique ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + /** A reference to the user presence that sent this data, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + /** Op code value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 OpCode = 0; + /** Data payload, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Data; + /** True if this data was delivered reliably, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Reliable = false; + + static FNakamaRtMatchData FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send realtime match data to the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchDataSend +{ + GENERATED_BODY() + /** The match unique ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + /** Op code value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 OpCode = 0; + /** Data payload, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Data; + /** List of presences in the match to deliver to, if filtering is required. Otherwise deliver to everyone in the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + /** True if the data should be sent reliably, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Reliable = false; + + static FNakamaRtMatchDataSend FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Join an existing realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchJoin +{ + GENERATED_BODY() + /** An optional set of key-value metadata pairs to be passed to the match handler, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Metadata; + + static FNakamaRtMatchJoin FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Leave a realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchLeave +{ + GENERATED_BODY() + /** The match unique ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + + static FNakamaRtMatchLeave FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A set of joins and leaves on a particular realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchPresenceEvent +{ + GENERATED_BODY() + /** The match unique ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + /** User presences that have just joined the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Joins; + /** User presences that have just left the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaves; + + static FNakamaRtMatchPresenceEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Start a new matchmaking process. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchmakerAdd +{ + GENERATED_BODY() + /** Minimum total user count to match together. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MinCount = 0; + /** Maximum total user count to match together. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxCount = 0; + /** Filter query used to identify suitable users. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Query; + /** Optional multiple of the count that must be satisfied. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 CountMultiple = 0; + /** String properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap StringProperties; + /** Numeric properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap NumericProperties; + + static FNakamaRtMatchmakerAdd FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchmakerMatched_MatchmakerUser +{ + GENERATED_BODY() + /** User info. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + /** Party identifier, if this user was matched as a party member. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** String properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap StringProperties; + /** Numeric properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap NumericProperties; + + static FNakamaRtMatchmakerMatched_MatchmakerUser FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A successful matchmaking result. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchmakerMatched +{ + GENERATED_BODY() + /** The matchmaking ticket that has completed. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Ticket; + /** The users that have been matched together, and information about their matchmaking data. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Users; + /** A reference to the current user and their properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtMatchmakerMatched_MatchmakerUser Self; + + static FNakamaRtMatchmakerMatched FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Cancel an existing ongoing matchmaking process. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchmakerRemove +{ + GENERATED_BODY() + /** The ticket to cancel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Ticket; + + static FNakamaRtMatchmakerRemove FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A ticket representing a new matchmaking process. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtMatchmakerTicket +{ + GENERATED_BODY() + /** The ticket that can be used to cancel matchmaking. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Ticket; + + static FNakamaRtMatchmakerTicket FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A collection of zero or more notifications. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtNotifications +{ + GENERATED_BODY() + /** Collection of notifications. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Notifications; + + static FNakamaRtNotifications FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Incoming information about a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtParty +{ + GENERATED_BODY() + /** Unique party identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Open flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Hidden flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Hidden = false; + /** Maximum number of party members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxSize = 0; + /** Self. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Self; + /** Leader. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Leader; + /** All current party members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + /** Label for party listing. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + + static FNakamaRtParty FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Create a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyCreate +{ + GENERATED_BODY() + /** Whether or not the party will require join requests to be approved by the party leader. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Maximum number of party members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxSize = 0; + /** Label */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + /** Whether the party is visible in party listings. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Hidden = false; + + static FNakamaRtPartyCreate FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Update a party label. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyUpdate +{ + GENERATED_BODY() + /** Party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Label to set. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + /** Change the party to open or closed. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Whether the party is visible in party listings. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Hidden = false; + + static FNakamaRtPartyUpdate FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Join a party, or request to join if the party is not open. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyJoin +{ + GENERATED_BODY() + /** Party ID to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + + static FNakamaRtPartyJoin FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Leave a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyLeave +{ + GENERATED_BODY() + /** Party ID to leave. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + + static FNakamaRtPartyLeave FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Promote a new party leader. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyPromote +{ + GENERATED_BODY() + /** Party ID to promote a new leader for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The presence of an existing party member to promote as the new leader. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + + static FNakamaRtPartyPromote FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Announcement of a new party leader. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyLeader +{ + GENERATED_BODY() + /** Party ID to announce the new leader for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The presence of the new party leader. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + + static FNakamaRtPartyLeader FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Accept a request to join. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyAccept +{ + GENERATED_BODY() + /** Party ID to accept a join request for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The presence to accept as a party member. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + + static FNakamaRtPartyAccept FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Kick a party member, or decline a request to join. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyRemove +{ + GENERATED_BODY() + /** Party ID to remove/reject from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The presence to remove or reject. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + + static FNakamaRtPartyRemove FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** End a party, kicking all party members and closing it. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyClose +{ + GENERATED_BODY() + /** Party ID to close. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + + static FNakamaRtPartyClose FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Request a list of pending join requests for a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyJoinRequestList +{ + GENERATED_BODY() + /** Party ID to get a list of join requests for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + + static FNakamaRtPartyJoinRequestList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Incoming notification for one or more new presences attempting to join the party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyJoinRequest +{ + GENERATED_BODY() + /** Party ID these presences are attempting to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Presences attempting to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + + static FNakamaRtPartyJoinRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Begin matchmaking as a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyMatchmakerAdd +{ + GENERATED_BODY() + /** Party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Minimum total user count to match together. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MinCount = 0; + /** Maximum total user count to match together. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxCount = 0; + /** Filter query used to identify suitable users. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Query; + /** Optional multiple of the count that must be satisfied. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 CountMultiple = 0; + /** String properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap StringProperties; + /** Numeric properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap NumericProperties; + + static FNakamaRtPartyMatchmakerAdd FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Cancel a party matchmaking process using a ticket. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyMatchmakerRemove +{ + GENERATED_BODY() + /** Party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The ticket to cancel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Ticket; + + static FNakamaRtPartyMatchmakerRemove FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A response from starting a new party matchmaking process. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyMatchmakerTicket +{ + GENERATED_BODY() + /** Party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** The ticket that can be used to cancel matchmaking. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Ticket; + + static FNakamaRtPartyMatchmakerTicket FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Incoming party data delivered from the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyData +{ + GENERATED_BODY() + /** The party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** A reference to the user presence that sent this data, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Presence; + /** Op code value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 OpCode = 0; + /** Data payload, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Data; + + static FNakamaRtPartyData FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send data to a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyDataSend +{ + GENERATED_BODY() + /** Party ID to send to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Op code value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 OpCode = 0; + /** Data payload, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Data; + + static FNakamaRtPartyDataSend FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Presence update for a particular party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPartyPresenceEvent +{ + GENERATED_BODY() + /** The party ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** User presences that have just joined the party. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Joins; + /** User presences that have just left the party. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaves; + + static FNakamaRtPartyPresenceEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Application-level heartbeat and connection check. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPing +{ + GENERATED_BODY() + + static FNakamaRtPing FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Application-level heartbeat and connection check response. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtPong +{ + GENERATED_BODY() + + static FNakamaRtPong FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A snapshot of statuses for some set of users. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStatus +{ + GENERATED_BODY() + /** User statuses. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Presences; + + static FNakamaRtStatus FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Start receiving status updates for some set of users. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStatusFollow +{ + GENERATED_BODY() + /** User IDs to follow. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + /** Usernames to follow. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Usernames; + + static FNakamaRtStatusFollow FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A batch of status updates for a given user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStatusPresenceEvent +{ + GENERATED_BODY() + /** New statuses for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Joins; + /** Previous statuses for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaves; + + static FNakamaRtStatusPresenceEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Stop receiving status updates for some set of users. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStatusUnfollow +{ + GENERATED_BODY() + /** Users to unfollow. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaRtStatusUnfollow FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Set the user's own status. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStatusUpdate +{ + GENERATED_BODY() + /** Status string to set, if not present the user will appear offline. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Status; + + static FNakamaRtStatusUpdate FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Represents identifying information for a stream. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStream +{ + GENERATED_BODY() + /** Mode identifies the type of stream. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Mode = 0; + /** Subject is the primary identifier, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Subject; + /** Subcontext is a secondary identifier, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Subcontext; + /** The label is an arbitrary identifying string, if the stream has one. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + + static FNakamaRtStream FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A data message delivered over a stream. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStreamData +{ + GENERATED_BODY() + /** The stream this data message relates to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtStream Stream; + /** The sender, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtUserPresence Sender; + /** Arbitrary contents of the data message. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Data; + /** True if this data was delivered reliably, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Reliable = false; + + static FNakamaRtStreamData FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A set of joins and leaves on a particular stream. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRtStreamPresenceEvent +{ + GENERATED_BODY() + /** The stream this event relates to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaRtStream Stream; + /** Presences joining the stream as part of this event, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Joins; + /** Presences leaving the stream as part of this event, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaves; + + static FNakamaRtStreamPresenceEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + diff --git a/Nakama/Source/NakamaApi/Public/NakamaTypes.h b/Nakama/Source/NakamaApi/Public/NakamaTypes.h new file mode 100644 index 000000000..61612cfe3 --- /dev/null +++ b/Nakama/Source/NakamaApi/Public/NakamaTypes.h @@ -0,0 +1,2955 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaTypes.generated.h" + +// Forward declarations +struct FNakamaUser; +struct FNakamaAccountDevice; +struct FNakamaAccount; +struct FNakamaAccountRefresh; +struct FNakamaAccountApple; +struct FNakamaAccountCustom; +struct FNakamaAccountEmail; +struct FNakamaAccountFacebook; +struct FNakamaAccountFacebookInstantGame; +struct FNakamaAccountGameCenter; +struct FNakamaAccountGoogle; +struct FNakamaAccountSteam; +struct FNakamaAddFriendsRequest; +struct FNakamaAddGroupUsersRequest; +struct FNakamaSessionRefreshRequest; +struct FNakamaSessionLogoutRequest; +struct FNakamaAuthenticateAppleRequest; +struct FNakamaAuthenticateCustomRequest; +struct FNakamaAuthenticateDeviceRequest; +struct FNakamaAuthenticateEmailRequest; +struct FNakamaAuthenticateFacebookRequest; +struct FNakamaAuthenticateFacebookInstantGameRequest; +struct FNakamaAuthenticateGameCenterRequest; +struct FNakamaAuthenticateGoogleRequest; +struct FNakamaAuthenticateSteamRequest; +struct FNakamaBanGroupUsersRequest; +struct FNakamaBlockFriendsRequest; +struct FNakamaChannelMessage; +struct FNakamaChannelMessageList; +struct FNakamaCreateGroupRequest; +struct FNakamaDeleteFriendsRequest; +struct FNakamaDeleteGroupRequest; +struct FNakamaDeleteLeaderboardRecordRequest; +struct FNakamaDeleteNotificationsRequest; +struct FNakamaDeleteTournamentRecordRequest; +struct FNakamaDeleteStorageObjectId; +struct FNakamaDeleteStorageObjectsRequest; +struct FNakamaEvent; +struct FNakamaFriend; +struct FNakamaFriendList; +struct FNakamaFriendsOfFriendsList_FriendOfFriend; +struct FNakamaFriendsOfFriendsList; +struct FNakamaGetUsersRequest; +struct FNakamaGetSubscriptionRequest; +struct FNakamaGroup; +struct FNakamaGroupList; +struct FNakamaGroupUserList_GroupUser; +struct FNakamaGroupUserList; +struct FNakamaImportFacebookFriendsRequest; +struct FNakamaImportSteamFriendsRequest; +struct FNakamaJoinGroupRequest; +struct FNakamaJoinTournamentRequest; +struct FNakamaKickGroupUsersRequest; +struct FNakamaLeaderboard; +struct FNakamaLeaderboardList; +struct FNakamaLeaderboardRecord; +struct FNakamaLeaderboardRecordList; +struct FNakamaLeaveGroupRequest; +struct FNakamaLinkFacebookRequest; +struct FNakamaLinkSteamRequest; +struct FNakamaListChannelMessagesRequest; +struct FNakamaListFriendsRequest; +struct FNakamaListFriendsOfFriendsRequest; +struct FNakamaListGroupsRequest; +struct FNakamaListGroupUsersRequest; +struct FNakamaListLeaderboardRecordsAroundOwnerRequest; +struct FNakamaListLeaderboardRecordsRequest; +struct FNakamaListMatchesRequest; +struct FNakamaListNotificationsRequest; +struct FNakamaListStorageObjectsRequest; +struct FNakamaListSubscriptionsRequest; +struct FNakamaListTournamentRecordsAroundOwnerRequest; +struct FNakamaListTournamentRecordsRequest; +struct FNakamaListTournamentsRequest; +struct FNakamaListUserGroupsRequest; +struct FNakamaMatch; +struct FNakamaMatchList; +struct FNakamaMatchmakerCompletionStats; +struct FNakamaMatchmakerStats; +struct FNakamaNotification; +struct FNakamaNotificationList; +struct FNakamaPromoteGroupUsersRequest; +struct FNakamaDemoteGroupUsersRequest; +struct FNakamaReadStorageObjectId; +struct FNakamaReadStorageObjectsRequest; +struct FNakamaRpc; +struct FNakamaSession; +struct FNakamaStorageObject; +struct FNakamaStorageObjectAck; +struct FNakamaStorageObjectAcks; +struct FNakamaStorageObjects; +struct FNakamaStorageObjectList; +struct FNakamaTournament; +struct FNakamaTournamentList; +struct FNakamaTournamentRecordList; +struct FNakamaUpdateAccountRequest; +struct FNakamaUpdateGroupRequest; +struct FNakamaUserGroupList_UserGroup; +struct FNakamaUserGroupList; +struct FNakamaUsers; +struct FNakamaValidatePurchaseAppleRequest; +struct FNakamaValidateSubscriptionAppleRequest; +struct FNakamaValidatePurchaseGoogleRequest; +struct FNakamaValidateSubscriptionGoogleRequest; +struct FNakamaValidatePurchaseHuaweiRequest; +struct FNakamaValidatePurchaseFacebookInstantRequest; +struct FNakamaValidatedPurchase; +struct FNakamaValidatePurchaseResponse; +struct FNakamaValidatedSubscription; +struct FNakamaValidateSubscriptionResponse; +struct FNakamaPurchaseList; +struct FNakamaSubscriptionList; +struct FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite; +struct FNakamaWriteLeaderboardRecordRequest; +struct FNakamaWriteStorageObject; +struct FNakamaWriteStorageObjectsRequest; +struct FNakamaWriteTournamentRecordRequest_TournamentRecordWrite; +struct FNakamaWriteTournamentRecordRequest; +struct FNakamaListPartiesRequest; +struct FNakamaParty; +struct FNakamaPartyList; + +/** The friendship status. */ +UENUM(BlueprintType) +enum class ENakamaFriend_State : uint8 +{ + FRIEND = 0, + INVITE_SENT = 1, + INVITE_RECEIVED = 2, + BLOCKED = 3 +}; + +/** The group role status. */ +UENUM(BlueprintType) +enum class ENakamaGroupUserList_GroupUser_State : uint8 +{ + SUPERADMIN = 0, + ADMIN = 1, + MEMBER = 2, + JOIN_REQUEST = 3 +}; + +/** The group role status. */ +UENUM(BlueprintType) +enum class ENakamaUserGroupList_UserGroup_State : uint8 +{ + SUPERADMIN = 0, + ADMIN = 1, + MEMBER = 2, + JOIN_REQUEST = 3 +}; + +/** Validation Provider, */ +UENUM(BlueprintType) +enum class ENakamaStoreProvider : uint8 +{ + APPLE_APP_STORE = 0, + GOOGLE_PLAY_STORE = 1, + HUAWEI_APP_GALLERY = 2, + FACEBOOK_INSTANT_STORE = 3 +}; + +/** Environment where a purchase/subscription took place, */ +UENUM(BlueprintType) +enum class ENakamaStoreEnvironment : uint8 +{ + UNKNOWN = 0, + SANDBOX = 1, + PRODUCTION = 2 +}; + +/** Operator that can be used to override the one set in the leaderboard. */ +UENUM(BlueprintType) +enum class ENakamaOperator : uint8 +{ + NO_OVERRIDE = 0, + BEST = 1, + SET = 2, + INCREMENT = 3, + DECREMENT = 4 +}; + +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaError +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Message; + + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; +}; +/** gRPC status codes returned by Nakama in FNakamaError::Code. */ +namespace ENakamaErrorCode +{ + constexpr int32 OK = 0; + constexpr int32 Cancelled = 1; + constexpr int32 Unknown = 2; + constexpr int32 InvalidArgument = 3; + constexpr int32 DeadlineExceeded = 4; + constexpr int32 NotFound = 5; + constexpr int32 AlreadyExists = 6; + constexpr int32 PermissionDenied = 7; + constexpr int32 ResourceExhausted = 8; + constexpr int32 FailedPrecondition = 9; + constexpr int32 Aborted = 10; + constexpr int32 OutOfRange = 11; + constexpr int32 Unimplemented = 12; + constexpr int32 Internal = 13; + constexpr int32 Unavailable = 14; + constexpr int32 DataLoss = 15; + constexpr int32 Unauthenticated = 16; +} + +// Forward declarations +struct FNakamaUser; +struct FNakamaAccountDevice; +struct FNakamaAccount; +struct FNakamaAccountRefresh; +struct FNakamaAccountApple; +struct FNakamaAccountCustom; +struct FNakamaAccountEmail; +struct FNakamaAccountFacebook; +struct FNakamaAccountFacebookInstantGame; +struct FNakamaAccountGameCenter; +struct FNakamaAccountGoogle; +struct FNakamaAccountSteam; +struct FNakamaAddFriendsRequest; +struct FNakamaAddGroupUsersRequest; +struct FNakamaSessionRefreshRequest; +struct FNakamaSessionLogoutRequest; +struct FNakamaAuthenticateAppleRequest; +struct FNakamaAuthenticateCustomRequest; +struct FNakamaAuthenticateDeviceRequest; +struct FNakamaAuthenticateEmailRequest; +struct FNakamaAuthenticateFacebookRequest; +struct FNakamaAuthenticateFacebookInstantGameRequest; +struct FNakamaAuthenticateGameCenterRequest; +struct FNakamaAuthenticateGoogleRequest; +struct FNakamaAuthenticateSteamRequest; +struct FNakamaBanGroupUsersRequest; +struct FNakamaBlockFriendsRequest; +struct FNakamaChannelMessage; +struct FNakamaChannelMessageList; +struct FNakamaCreateGroupRequest; +struct FNakamaDeleteFriendsRequest; +struct FNakamaDeleteGroupRequest; +struct FNakamaDeleteLeaderboardRecordRequest; +struct FNakamaDeleteNotificationsRequest; +struct FNakamaDeleteTournamentRecordRequest; +struct FNakamaDeleteStorageObjectId; +struct FNakamaDeleteStorageObjectsRequest; +struct FNakamaEvent; +struct FNakamaFriend; +struct FNakamaFriendList; +struct FNakamaFriendsOfFriendsList_FriendOfFriend; +struct FNakamaFriendsOfFriendsList; +struct FNakamaGetUsersRequest; +struct FNakamaGetSubscriptionRequest; +struct FNakamaGroup; +struct FNakamaGroupList; +struct FNakamaGroupUserList_GroupUser; +struct FNakamaGroupUserList; +struct FNakamaImportFacebookFriendsRequest; +struct FNakamaImportSteamFriendsRequest; +struct FNakamaJoinGroupRequest; +struct FNakamaJoinTournamentRequest; +struct FNakamaKickGroupUsersRequest; +struct FNakamaLeaderboard; +struct FNakamaLeaderboardList; +struct FNakamaLeaderboardRecord; +struct FNakamaLeaderboardRecordList; +struct FNakamaLeaveGroupRequest; +struct FNakamaLinkFacebookRequest; +struct FNakamaLinkSteamRequest; +struct FNakamaListChannelMessagesRequest; +struct FNakamaListFriendsRequest; +struct FNakamaListFriendsOfFriendsRequest; +struct FNakamaListGroupsRequest; +struct FNakamaListGroupUsersRequest; +struct FNakamaListLeaderboardRecordsAroundOwnerRequest; +struct FNakamaListLeaderboardRecordsRequest; +struct FNakamaListMatchesRequest; +struct FNakamaListNotificationsRequest; +struct FNakamaListStorageObjectsRequest; +struct FNakamaListSubscriptionsRequest; +struct FNakamaListTournamentRecordsAroundOwnerRequest; +struct FNakamaListTournamentRecordsRequest; +struct FNakamaListTournamentsRequest; +struct FNakamaListUserGroupsRequest; +struct FNakamaMatch; +struct FNakamaMatchList; +struct FNakamaMatchmakerCompletionStats; +struct FNakamaMatchmakerStats; +struct FNakamaNotification; +struct FNakamaNotificationList; +struct FNakamaPromoteGroupUsersRequest; +struct FNakamaDemoteGroupUsersRequest; +struct FNakamaReadStorageObjectId; +struct FNakamaReadStorageObjectsRequest; +struct FNakamaRpc; +struct FNakamaSession; +struct FNakamaStorageObject; +struct FNakamaStorageObjectAck; +struct FNakamaStorageObjectAcks; +struct FNakamaStorageObjects; +struct FNakamaStorageObjectList; +struct FNakamaTournament; +struct FNakamaTournamentList; +struct FNakamaTournamentRecordList; +struct FNakamaUpdateAccountRequest; +struct FNakamaUpdateGroupRequest; +struct FNakamaUserGroupList_UserGroup; +struct FNakamaUserGroupList; +struct FNakamaUsers; +struct FNakamaValidatePurchaseAppleRequest; +struct FNakamaValidateSubscriptionAppleRequest; +struct FNakamaValidatePurchaseGoogleRequest; +struct FNakamaValidateSubscriptionGoogleRequest; +struct FNakamaValidatePurchaseHuaweiRequest; +struct FNakamaValidatePurchaseFacebookInstantRequest; +struct FNakamaValidatedPurchase; +struct FNakamaValidatePurchaseResponse; +struct FNakamaValidatedSubscription; +struct FNakamaValidateSubscriptionResponse; +struct FNakamaPurchaseList; +struct FNakamaSubscriptionList; +struct FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite; +struct FNakamaWriteLeaderboardRecordRequest; +struct FNakamaWriteStorageObject; +struct FNakamaWriteStorageObjectsRequest; +struct FNakamaWriteTournamentRecordRequest_TournamentRecordWrite; +struct FNakamaWriteTournamentRecordRequest; +struct FNakamaListPartiesRequest; +struct FNakamaParty; +struct FNakamaPartyList; + +/** A user in the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUser +{ + GENERATED_BODY() + /** The id of the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** The username of the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** The display name of the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString DisplayName; + /** A URL for an avatar image. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AvatarUrl; + /** The language expected to be a tag which follows the BCP-47 spec. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** The location set by the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Location; + /** The timezone set by the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Timezone; + /** Additional information stored as a JSON object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** The Facebook id in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString FacebookId; + /** The Google id in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GoogleId; + /** The Apple Game Center in of the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GamecenterId; + /** The Steam id in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SteamId; + /** Indicates whether the user is currently online. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Online = false; + /** Number of related edges to this user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 EdgeCount = 0; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** The Facebook Instant Game ID in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString FacebookInstantGameId; + /** The Apple Sign In ID in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AppleId; + + static FNakamaUser FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a device to the server. Used with authenticate/link/unlink and user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountDevice +{ + GENERATED_BODY() + /** A device identifier. Should be obtained by a platform-specific device API. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountDevice FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A user with additional account details. Always the current user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccount +{ + GENERATED_BODY() + /** The user object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaUser User; + /** The user's wallet data. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Wallet; + /** The email address of the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Email; + /** The devices which belong to the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Devices; + /** The custom id in the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CustomId; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's email was verified. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString VerifyTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's account was disabled/banned. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString DisableTime; + + static FNakamaAccount FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Obtain a new authentication token using a refresh token. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountRefresh +{ + GENERATED_BODY() + /** Refresh token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountRefresh FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a Apple Sign In token to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountApple +{ + GENERATED_BODY() + /** The ID token received from Apple to validate. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountApple FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a custom ID to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountCustom +{ + GENERATED_BODY() + /** A custom identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountCustom FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send an email with password to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountEmail +{ + GENERATED_BODY() + /** A valid RFC-5322 email address. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Email; + /** A password for the user account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Password; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountEmail FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a Facebook token to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountFacebook +{ + GENERATED_BODY() + /** The OAuth token received from Facebook to access their profile API. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountFacebook FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a Facebook Instant Game token to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountFacebookInstantGame +{ + GENERATED_BODY() + /** The OAuth token received from a Facebook Instant Game that may be decoded with the Application Secret (must be available with the nakama configuration) */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SignedPlayerInfo; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountFacebookInstantGame FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send Apple's Game Center account credentials to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountGameCenter +{ + GENERATED_BODY() + /** Player ID (generated by GameCenter). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PlayerId; + /** Bundle ID (generated by GameCenter). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString BundleId; + /** Time since UNIX epoch when the signature was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 TimestampSeconds = 0; + /** A random "NSString" used to compute the hash and keep it randomized. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Salt; + /** The verification signature data generated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Signature; + /** The URL for the public encryption key. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PublicKeyUrl; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountGameCenter FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a Google token to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountGoogle +{ + GENERATED_BODY() + /** The OAuth token received from Google to access their profile API. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountGoogle FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Send a Steam token to the server. Used with authenticate/link/unlink. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAccountSteam +{ + GENERATED_BODY() + /** The account token received from Steam to access their profile API. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaAccountSteam FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Add one or more friends to the current user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAddFriendsRequest +{ + GENERATED_BODY() + /** The account id of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Ids; + /** The account username of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Usernames; + /** Optional metadata to add to friends. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + + static FNakamaAddFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Add users to a group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAddGroupUsersRequest +{ + GENERATED_BODY() + /** The group to add users to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The users to add. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaAddGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with a refresh token. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaSessionRefreshRequest +{ + GENERATED_BODY() + /** Refresh token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Extra information that will be bundled in the session token. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Vars; + + static FNakamaSessionRefreshRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaSessionLogoutRequest +{ + GENERATED_BODY() + /** Session token to log out. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Refresh token to invalidate. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RefreshToken; + + static FNakamaSessionLogoutRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Apple Sign In. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateAppleRequest +{ + GENERATED_BODY() + /** The Apple account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountApple Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateAppleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with a custom ID. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateCustomRequest +{ + GENERATED_BODY() + /** The custom account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountCustom Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateCustomRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with a device ID. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateDeviceRequest +{ + GENERATED_BODY() + /** The device account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountDevice Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateDeviceRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with email+password. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateEmailRequest +{ + GENERATED_BODY() + /** The email account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountEmail Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateEmailRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Facebook. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateFacebookRequest +{ + GENERATED_BODY() + /** The Facebook account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountFacebook Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** Import Facebook friends for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Sync = false; + + static FNakamaAuthenticateFacebookRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Facebook Instant Game token. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateFacebookInstantGameRequest +{ + GENERATED_BODY() + /** The Facebook Instant Game account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountFacebookInstantGame Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateFacebookInstantGameRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Apple's Game Center. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateGameCenterRequest +{ + GENERATED_BODY() + /** The Game Center account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountGameCenter Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateGameCenterRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Google. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateGoogleRequest +{ + GENERATED_BODY() + /** The Google account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountGoogle Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + + static FNakamaAuthenticateGoogleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with Steam. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaAuthenticateSteamRequest +{ + GENERATED_BODY() + /** The Steam account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountSteam Account; + /** Register the account if the user does not already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Create = false; + /** Set the username on the account at register. Must be unique. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** Import Steam friends for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Sync = false; + + static FNakamaAuthenticateSteamRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Ban users from a group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaBanGroupUsersRequest +{ + GENERATED_BODY() + /** The group to ban users from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The users to ban. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaBanGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Block one or more friends for the current user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaBlockFriendsRequest +{ + GENERATED_BODY() + /** The account id of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Ids; + /** The account username of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Usernames; + + static FNakamaBlockFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A message sent on a channel. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaChannelMessage +{ + GENERATED_BODY() + /** The channel this message belongs to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** The unique ID of this message. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MessageId; + /** The code representing a message type or category. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; + /** Message sender, usually a user ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SenderId; + /** The username of the message sender, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** The content payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Content; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** True if the message was persisted to the channel's history, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persistent = false; + /** The name of the chat room, or an empty string if this message was not sent through a chat room. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RoomName; + /** The ID of the group, or an empty string if this message was not sent through a group channel. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The ID of the first DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdOne; + /** The ID of the second DM user, or an empty string if this message was not sent through a DM chat. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserIdTwo; + + static FNakamaChannelMessage FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of channel messages, usually a result of a list operation. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaChannelMessageList +{ + GENERATED_BODY() + /** A list of messages. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Messages; + /** The cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString NextCursor; + /** The cursor to send when retrieving the previous page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PrevCursor; + /** Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CacheableCursor; + + static FNakamaChannelMessageList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Create a group with the current user as owner. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaCreateGroupRequest +{ + GENERATED_BODY() + /** A unique name for the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + /** A description for the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Description; + /** The language expected to be a tag which follows the BCP-47 spec. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** A URL for an avatar image. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AvatarUrl; + /** Mark a group as open or not where only admins can accept members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Maximum number of group members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxCount = 0; + + static FNakamaCreateGroupRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Delete one or more friends for the current user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteFriendsRequest +{ + GENERATED_BODY() + /** The account id of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Ids; + /** The account username of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Usernames; + + static FNakamaDeleteFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Delete a group the user has access to. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteGroupRequest +{ + GENERATED_BODY() + /** The id of a group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + + static FNakamaDeleteGroupRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Delete a leaderboard record. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteLeaderboardRecordRequest +{ + GENERATED_BODY() + /** The leaderboard ID to delete from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LeaderboardId; + + static FNakamaDeleteLeaderboardRecordRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Delete one or more notifications for the current user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteNotificationsRequest +{ + GENERATED_BODY() + /** The id of notifications. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Ids; + + static FNakamaDeleteNotificationsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Delete a leaderboard record. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteTournamentRecordRequest +{ + GENERATED_BODY() + /** The tournament ID to delete from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TournamentId; + + static FNakamaDeleteTournamentRecordRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Storage objects to delete. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteStorageObjectId +{ + GENERATED_BODY() + /** The collection which stores the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The key of the object within the collection. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Key; + /** The version hash of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Version; + + static FNakamaDeleteStorageObjectId FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Batch delete storage objects. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDeleteStorageObjectsRequest +{ + GENERATED_BODY() + /** Batch of storage objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray ObjectIds; + + static FNakamaDeleteStorageObjectsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Represents an event to be passed through the server to registered event handlers. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaEvent +{ + GENERATED_BODY() + /** An event name, type, category, or identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + /** The time when the event was triggered. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Timestamp; + /** True if the event came directly from a client call, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool External = false; + /** Arbitrary event property values. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TMap Properties; + + static FNakamaEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A friend of a user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaFriend +{ + GENERATED_BODY() + /** The user object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaUser User; + /** The friend status. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + /** Time of the latest relationship update. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** Metadata. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + + static FNakamaFriend FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A collection of zero or more friends of the user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaFriendList +{ + GENERATED_BODY() + /** The Friend objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Friends; + /** Cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaFriendList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A friend of a friend. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaFriendsOfFriendsList_FriendOfFriend +{ + GENERATED_BODY() + /** The user who referred its friend. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Referrer; + /** User. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaUser User; + + static FNakamaFriendsOfFriendsList_FriendOfFriend FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A List of friends of friends */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaFriendsOfFriendsList +{ + GENERATED_BODY() + /** User friends of friends. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray FriendsOfFriends; + /** Cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaFriendsOfFriendsList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Fetch a batch of zero or more users from the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGetUsersRequest +{ + GENERATED_BODY() + /** The account id of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Ids; + /** The account username of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Usernames; + /** The Facebook ID of a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray FacebookIds; + + static FNakamaGetUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Fetch a subscription by product id. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGetSubscriptionRequest +{ + GENERATED_BODY() + /** Product id of the subscription */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProductId; + + static FNakamaGetSubscriptionRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A group in the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGroup +{ + GENERATED_BODY() + /** The id of a group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** The id of the user who created the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreatorId; + /** The unique name of the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + /** A description for the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Description; + /** The language expected to be a tag which follows the BCP-47 spec. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** Additional information stored as a JSON object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** A URL for an avatar image. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AvatarUrl; + /** Anyone can join open groups, otherwise only admins can accept members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** The current count of all members in the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 EdgeCount = 0; + /** The maximum number of members allowed. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxCount = 0; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + + static FNakamaGroup FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** One or more groups returned from a listing operation. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGroupList +{ + GENERATED_BODY() + /** One or more groups. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Groups; + /** A cursor used to get the next page. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaGroupList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGroupUserList_GroupUser +{ + GENERATED_BODY() + /** User. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaUser User; + /** Their relationship to the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + + static FNakamaGroupUserList_GroupUser FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of users belonging to a group, along with their role. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaGroupUserList +{ + GENERATED_BODY() + /** User-role pairs for a group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray GroupUsers; + /** Cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaGroupUserList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Import Facebook friends into the current user's account. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaImportFacebookFriendsRequest +{ + GENERATED_BODY() + /** The Facebook account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountFacebook Account; + /** Reset the current user's friends list. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Reset = false; + + static FNakamaImportFacebookFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Import Facebook friends into the current user's account. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaImportSteamFriendsRequest +{ + GENERATED_BODY() + /** The Facebook account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountSteam Account; + /** Reset the current user's friends list. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Reset = false; + + static FNakamaImportSteamFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Immediately join an open group, or request to join a closed one. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaJoinGroupRequest +{ + GENERATED_BODY() + /** The group ID to join. The group must already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + + static FNakamaJoinGroupRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The request to join a tournament. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaJoinTournamentRequest +{ + GENERATED_BODY() + /** The ID of the tournament to join. The tournament must already exist. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TournamentId; + + static FNakamaJoinTournamentRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Kick a set of users from a group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaKickGroupUsersRequest +{ + GENERATED_BODY() + /** The group ID to kick from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The users to kick. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaKickGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A leaderboard on the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLeaderboard +{ + GENERATED_BODY() + /** The ID of the leaderboard. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** ASC(0) or DESC(1) sort mode of scores in the leaderboard. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 SortOrder = 0; + /** BEST, SET, INCREMENT or DECREMENT operator mode of the leaderboard. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaOperator Operator = static_cast(0); + /** The UNIX time when the leaderboard was previously reset. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PrevReset = 0; + /** The UNIX time when the leaderboard is next playable. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 NextReset = 0; + /** Additional information stored as a JSON object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** Whether the leaderboard was created authoritatively or not. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Authoritative = false; + + static FNakamaLeaderboard FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of leaderboards */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLeaderboardList +{ + GENERATED_BODY() + /** The list of leaderboards returned. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Leaderboards; + /** A pagination cursor (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaLeaderboardList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Represents a complete leaderboard record with all scores and associated metadata. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLeaderboardRecord +{ + GENERATED_BODY() + /** The ID of the leaderboard this score belongs to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LeaderboardId; + /** The ID of the score owner, usually a user or group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString OwnerId; + /** The username of the score owner, if the owner is a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** The score value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Score = 0; + /** An optional subscore value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Subscore = 0; + /** The number of submissions to this score record. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 NumScore = 0; + /** Metadata. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record expires. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ExpiryTime; + /** The rank of this record. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Rank = 0; + /** The maximum number of score updates allowed by the owner. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxNumScore = 0; + + static FNakamaLeaderboardRecord FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A set of leaderboard records, may be part of a leaderboard records page or a batch of individual records. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLeaderboardRecordList +{ + GENERATED_BODY() + /** A list of leaderboard records. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Records; + /** A batched set of leaderboard records belonging to specified owners. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray OwnerRecords; + /** The cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString NextCursor; + /** The cursor to send when retrieving the previous page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PrevCursor; + /** The total number of ranks available. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 RankCount = 0; + + static FNakamaLeaderboardRecordList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Leave a group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLeaveGroupRequest +{ + GENERATED_BODY() + /** The group ID to leave. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + + static FNakamaLeaveGroupRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Link Facebook to the current user's account. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLinkFacebookRequest +{ + GENERATED_BODY() + /** The Facebook account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountFacebook Account; + /** Import Facebook friends for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Sync = false; + + static FNakamaLinkFacebookRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Link Steam to the current user's account. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaLinkSteamRequest +{ + GENERATED_BODY() + /** The Facebook account details. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaAccountSteam Account; + /** Import Steam friends for the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Sync = false; + + static FNakamaLinkSteamRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List a channel's message history. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListChannelMessagesRequest +{ + GENERATED_BODY() + /** The channel ID to list from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ChannelId; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** True if listing should be older messages to newer, false if reverse. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Forward = false; + /** A pagination cursor, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListChannelMessagesRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List friends for a user. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListFriendsRequest +{ + GENERATED_BODY() + /** Max number of records to return. Between 1 and 1000. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The friend state to list. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + /** An optional next page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListFriendsOfFriendsRequest +{ + GENERATED_BODY() + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** An optional next page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListFriendsOfFriendsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List groups based on given filters. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListGroupsRequest +{ + GENERATED_BODY() + /** List groups that contain this value in their names. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + /** Optional pagination cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + /** Max number of groups to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** Language tag filter */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** Number of group members */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Members = 0; + /** Optional Open/Closed filter. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + + static FNakamaListGroupsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List all users that are part of a group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListGroupUsersRequest +{ + GENERATED_BODY() + /** The group ID to list from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The group user state to list. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + /** An optional next page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List leaerboard records from a given leaderboard around the owner. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListLeaderboardRecordsAroundOwnerRequest +{ + GENERATED_BODY() + /** The ID of the tournament to list for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LeaderboardId; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The owner to retrieve records around. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString OwnerId; + /** Expiry in seconds (since epoch) to begin fetching records from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Expiry = 0; + /** A next or previous page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListLeaderboardRecordsAroundOwnerRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List leaderboard records from a given leaderboard. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListLeaderboardRecordsRequest +{ + GENERATED_BODY() + /** The ID of the leaderboard to list for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LeaderboardId; + /** One or more owners to retrieve records for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray OwnerIds; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** A next or previous page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + /** Expiry in seconds (since epoch) to begin fetching records from. Optional. 0 means from current time. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Expiry = 0; + + static FNakamaListLeaderboardRecordsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List realtime matches. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListMatchesRequest +{ + GENERATED_BODY() + /** Limit the number of returned matches. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** Authoritative or relayed matches. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Authoritative = false; + /** Label filter. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + /** Minimum user count. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MinSize = 0; + /** Maximum user count. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxSize = 0; + /** Arbitrary label query. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Query; + + static FNakamaListMatchesRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Get a list of unexpired notifications. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListNotificationsRequest +{ + GENERATED_BODY() + /** The number of notifications to get. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** A cursor to page through notifications. May be cached by clients to get from point in time forwards. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CacheableCursor; + + static FNakamaListNotificationsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List publicly readable storage objects in a given collection. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListStorageObjectsRequest +{ + GENERATED_BODY() + /** ID of the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** The collection which stores the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The number of storage objects to list. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The cursor to page through results from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListStorageObjectsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List user subscriptions. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListSubscriptionsRequest +{ + GENERATED_BODY() + /** Max number of results per page */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** Cursor to retrieve a page of records from */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListSubscriptionsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List tournament records from a given tournament around the owner. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListTournamentRecordsAroundOwnerRequest +{ + GENERATED_BODY() + /** The ID of the tournament to list for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TournamentId; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The owner to retrieve records around. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString OwnerId; + /** Expiry in seconds (since epoch) to begin fetching records from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Expiry = 0; + /** A next or previous page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListTournamentRecordsAroundOwnerRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List tournament records from a given tournament. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListTournamentRecordsRequest +{ + GENERATED_BODY() + /** The ID of the tournament to list for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TournamentId; + /** One or more owners to retrieve records for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray OwnerIds; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** A next or previous page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + /** Expiry in seconds (since epoch) to begin fetching records from. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Expiry = 0; + + static FNakamaListTournamentRecordsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List active/upcoming tournaments based on given filters. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListTournamentsRequest +{ + GENERATED_BODY() + /** The start of the categories to include. Defaults to 0. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 CategoryStart = 0; + /** The end of the categories to include. Defaults to 128. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 CategoryEnd = 0; + /** The start time for tournaments. Defaults to epoch. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 StartTime = 0; + /** The end time for tournaments. Defaults to +1 year from current Unix time. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 EndTime = 0; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** A next page cursor for listings (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListTournamentsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List the groups a user is part of, and their relationship to each. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListUserGroupsRequest +{ + GENERATED_BODY() + /** ID of the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** Max number of records to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** The user group state to list. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + /** An optional next page cursor. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListUserGroupsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Represents a realtime match. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaMatch +{ + GENERATED_BODY() + /** The ID of the match, can be used to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString MatchId; + /** True if it's an server-managed authoritative match, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Authoritative = false; + /** Match label, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + /** Current number of users in the match. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Size = 0; + /** Tick Rate */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 TickRate = 0; + /** Handler name */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString HandlerName; + + static FNakamaMatch FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of realtime matches. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaMatchList +{ + GENERATED_BODY() + /** A number of matches corresponding to a list operation. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Matches; + + static FNakamaMatchList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Matchmaker ticket completion stats */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaMatchmakerCompletionStats +{ + GENERATED_BODY() + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CompleteTime; + + static FNakamaMatchmakerCompletionStats FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Matchmaker stats */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaMatchmakerStats +{ + GENERATED_BODY() + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 TicketCount = 0; + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString OldestTicketCreateTime; + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Completions; + + static FNakamaMatchmakerStats FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A notification in the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaNotification +{ + GENERATED_BODY() + /** ID of the Notification. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** Subject of the notification. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Subject; + /** Content of the notification in JSON. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Content; + /** Category code for this notification. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; + /** ID of the sender, if a user. Otherwise 'null'. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SenderId; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the notification was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** True if this notification was persisted to the database. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persistent = false; + + static FNakamaNotification FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A collection of zero or more notifications. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaNotificationList +{ + GENERATED_BODY() + /** Collection of notifications. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Notifications; + /** Use this cursor to paginate notifications. Cache this to catch up to new notifications. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CacheableCursor; + + static FNakamaNotificationList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Promote a set of users in a group to the next role up. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaPromoteGroupUsersRequest +{ + GENERATED_BODY() + /** The group ID to promote in. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The users to promote. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaPromoteGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Demote a set of users in a group to the next role down. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaDemoteGroupUsersRequest +{ + GENERATED_BODY() + /** The group ID to demote in. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** The users to demote. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserIds; + + static FNakamaDemoteGroupUsersRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Storage objects to get. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaReadStorageObjectId +{ + GENERATED_BODY() + /** The collection which stores the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The key of the object within the collection. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Key; + /** The user owner of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + + static FNakamaReadStorageObjectId FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Batch get storage objects. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaReadStorageObjectsRequest +{ + GENERATED_BODY() + /** Batch of storage objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray ObjectIds; + + static FNakamaReadStorageObjectsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Execute an Lua function on the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRpc +{ + GENERATED_BODY() + /** The identifier of the function. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** The payload of the function which must be a JSON object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Payload; + /** The authentication key used when executed as a non-client HTTP request. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString HttpKey; + + static FNakamaRpc FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A user's session used to authenticate messages. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaSession +{ + GENERATED_BODY() + /** True if the corresponding account was just created, false otherwise. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Created = false; + /** Authentication credentials. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Token; + /** Refresh token that can be used for session token renewal. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RefreshToken; + + /** User ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + FString UserId; + + /** Username parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + FString Username; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 RefreshTokenExpiresAt = 0; + + /** Session variables from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + TMap Vars; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static FNakamaSession FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; + +/** An object within the storage engine. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaStorageObject +{ + GENERATED_BODY() + /** The collection which stores the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The key of the object within the collection. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Key; + /** The user owner of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** The value of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Value; + /** The version hash of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Version; + /** The read access permissions for the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PermissionRead = 0; + /** The write access permissions for the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PermissionWrite = 0; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + + static FNakamaStorageObject FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A storage acknowledgement. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaStorageObjectAck +{ + GENERATED_BODY() + /** The collection which stores the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The key of the object within the collection. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Key; + /** The version hash of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Version; + /** The owner of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + + static FNakamaStorageObjectAck FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Batch of acknowledgements for the storage object write. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaStorageObjectAcks +{ + GENERATED_BODY() + /** Batch of storage write acknowledgements. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Acks; + + static FNakamaStorageObjectAcks FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Batch of storage objects. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaStorageObjects +{ + GENERATED_BODY() + /** The batch of storage objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Objects; + + static FNakamaStorageObjects FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List of storage objects. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaStorageObjectList +{ + GENERATED_BODY() + /** The list of storage objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Objects; + /** The cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaStorageObjectList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A tournament on the server. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaTournament +{ + GENERATED_BODY() + /** The ID of the tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Id; + /** The title for the tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Title; + /** The description of the tournament. May be blank. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Description; + /** The category of the tournament. e.g. "vip" could be category 1. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Category = 0; + /** ASC (0) or DESC (1) sort mode of scores in the tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 SortOrder = 0; + /** The current number of players in the tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Size = 0; + /** The maximum number of players for the tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxSize = 0; + /** The maximum score updates allowed per player for the current tournament. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxNumScore = 0; + /** True if the tournament is active and can enter. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool CanEnter = false; + /** The UNIX time when the tournament stops being active until next reset. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 EndActive = 0; + /** The UNIX time when the tournament is next playable. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 NextReset = 0; + /** Additional information stored as a JSON object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will start. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString StartTime; + /** The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will be stopped. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString EndTime; + /** Duration of the tournament in seconds. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Duration = 0; + /** The UNIX time when the tournament start being active. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 StartActive = 0; + /** The UNIX time when the tournament was last reset. A computed value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PrevReset = 0; + /** Operator. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaOperator Operator = static_cast(0); + /** Whether the leaderboard was created authoritatively or not. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Authoritative = false; + /** Whether the user must join the tournament before being able to submit scores. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool JoinRequired = false; + + static FNakamaTournament FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of tournaments. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaTournamentList +{ + GENERATED_BODY() + /** The list of tournaments returned. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Tournaments; + /** A pagination cursor (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaTournamentList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A set of tournament records which may be part of a tournament records page or a batch of individual records. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaTournamentRecordList +{ + GENERATED_BODY() + /** A list of tournament records. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Records; + /** A batched set of tournament records belonging to specified owners. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray OwnerRecords; + /** The cursor to send when retireving the next page (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString NextCursor; + /** The cursor to send when retrieving the previous page (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PrevCursor; + /** The total number of ranks available. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 RankCount = 0; + + static FNakamaTournamentRecordList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Update a user's account details. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUpdateAccountRequest +{ + GENERATED_BODY() + /** The username of the user's account. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Username; + /** The display name of the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString DisplayName; + /** A URL for an avatar image. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AvatarUrl; + /** The language expected to be a tag which follows the BCP-47 spec. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** The location set by the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Location; + /** The timezone set by the user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Timezone; + + static FNakamaUpdateAccountRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Update fields in a given group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUpdateGroupRequest +{ + GENERATED_BODY() + /** The ID of the group to update. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString GroupId; + /** Name. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Name; + /** Description string. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Description; + /** Lang tag. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LangTag; + /** Avatar URL. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString AvatarUrl; + /** Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + + static FNakamaUpdateGroupRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A single group-role pair. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUserGroupList_UserGroup +{ + GENERATED_BODY() + /** Group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaGroup Group; + /** The user's relationship to the group. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 State = 0; + + static FNakamaUserGroupList_UserGroup FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of groups belonging to a user, along with the user's role in each group. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUserGroupList +{ + GENERATED_BODY() + /** Group-role pairs for a user. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray UserGroups; + /** Cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaUserGroupList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A collection of zero or more users. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaUsers +{ + GENERATED_BODY() + /** The User objects. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Users; + + static FNakamaUsers FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Apple IAP Purchases validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatePurchaseAppleRequest +{ + GENERATED_BODY() + /** Base64 encoded Apple receipt data payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Receipt; + /** Persist the purchase */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidatePurchaseAppleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Apple Subscription validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidateSubscriptionAppleRequest +{ + GENERATED_BODY() + /** Base64 encoded Apple receipt data payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Receipt; + /** Persist the subscription. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidateSubscriptionAppleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Google IAP Purchase validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatePurchaseGoogleRequest +{ + GENERATED_BODY() + /** JSON encoded Google purchase payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Purchase; + /** Persist the purchase */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidatePurchaseGoogleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Google Subscription validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidateSubscriptionGoogleRequest +{ + GENERATED_BODY() + /** JSON encoded Google purchase payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Receipt; + /** Persist the subscription. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidateSubscriptionGoogleRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Huawei IAP Purchase validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatePurchaseHuaweiRequest +{ + GENERATED_BODY() + /** JSON encoded Huawei InAppPurchaseData. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Purchase; + /** InAppPurchaseData signature. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Signature; + /** Persist the purchase */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidatePurchaseHuaweiRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Facebook Instant IAP Purchase validation request */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatePurchaseFacebookInstantRequest +{ + GENERATED_BODY() + /** Base64 encoded Facebook Instant signedRequest receipt data payload. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString SignedRequest; + /** Persist the purchase */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Persist = false; + + static FNakamaValidatePurchaseFacebookInstantRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Validated Purchase stored by Nakama. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatedPurchase +{ + GENERATED_BODY() + /** Purchase User ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** Purchase Product ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProductId; + /** Purchase Transaction ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TransactionId; + /** Store identifier */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaStoreProvider Store = static_cast(0); + /** Timestamp when the purchase was done. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PurchaseTime; + /** Timestamp when the receipt validation was stored in DB. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** Timestamp when the receipt validation was updated in DB. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** Timestamp when the purchase was refunded. Set to UNIX */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RefundTime; + /** Raw provider validation response. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProviderResponse; + /** Whether the purchase was done in production or sandbox environment. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaStoreEnvironment Environment = static_cast(0); + /** Whether the purchase had already been validated by Nakama before. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool SeenBefore = false; + + static FNakamaValidatedPurchase FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Validate IAP response. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatePurchaseResponse +{ + GENERATED_BODY() + /** Newly seen validated purchases. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray ValidatedPurchases; + + static FNakamaValidatePurchaseResponse FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidatedSubscription +{ + GENERATED_BODY() + /** Subscription User ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UserId; + /** Purchase Product ID. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProductId; + /** Purchase Original transaction ID (we only keep track of the original subscription, not subsequent renewals). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString OriginalTransactionId; + /** Store identifier */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaStoreProvider Store = static_cast(0); + /** UNIX Timestamp when the purchase was done. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PurchaseTime; + /** UNIX Timestamp when the receipt validation was stored in DB. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString CreateTime; + /** UNIX Timestamp when the receipt validation was updated in DB. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString UpdateTime; + /** Whether the purchase was done in production or sandbox environment. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaStoreEnvironment Environment = static_cast(0); + /** Subscription expiration time. The subscription can still be auto-renewed to extend the expiration time further. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ExpiryTime; + /** Subscription refund time. If this time is set, the subscription was refunded. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString RefundTime; + /** Raw provider validation response body. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProviderResponse; + /** Raw provider notification body. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString ProviderNotification; + /** Whether the subscription is currently active or not. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Active = false; + + static FNakamaValidatedSubscription FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Validate Subscription response. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaValidateSubscriptionResponse +{ + GENERATED_BODY() + /** */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaValidatedSubscription ValidatedSubscription; + + static FNakamaValidateSubscriptionResponse FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of validated purchases stored by Nakama. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaPurchaseList +{ + GENERATED_BODY() + /** Stored validated purchases. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray ValidatedPurchases; + /** The cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + /** The cursor to send when retrieving the previous page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PrevCursor; + + static FNakamaPurchaseList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of validated subscriptions stored by Nakama. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaSubscriptionList +{ + GENERATED_BODY() + /** Stored validated subscriptions. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray ValidatedSubscriptions; + /** The cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + /** The cursor to send when retrieving the previous page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PrevCursor; + + static FNakamaSubscriptionList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Record values to write. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite +{ + GENERATED_BODY() + /** The score value to submit. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Score = 0; + /** An optional secondary value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Subscore = 0; + /** Optional record metadata. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** Operator override. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaOperator Operator = static_cast(0); + + static FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A request to submit a score to a leaderboard. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteLeaderboardRecordRequest +{ + GENERATED_BODY() + /** The ID of the leaderboard to write to. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString LeaderboardId; + /** Record input. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite Record; + + static FNakamaWriteLeaderboardRecordRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The object to store. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteStorageObject +{ + GENERATED_BODY() + /** The collection to store the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Collection; + /** The key for the object within the collection. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Key; + /** The value of the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Value; + /** The version hash of the object to check. Possible values are: ["", "*", "#hash#"]. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Version; + /** The read access permissions for the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PermissionRead = 0; + /** The write access permissions for the object. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 PermissionWrite = 0; + + static FNakamaWriteStorageObject FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Write objects to the storage engine. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteStorageObjectsRequest +{ + GENERATED_BODY() + /** The objects to store on the server. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Objects; + + static FNakamaWriteStorageObjectsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Record values to write. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteTournamentRecordRequest_TournamentRecordWrite +{ + GENERATED_BODY() + /** The score value to submit. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Score = 0; + /** An optional secondary value. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int64 Subscore = 0; + /** A JSON object of additional properties (optional). */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Metadata; + /** Operator override. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + ENakamaOperator Operator = static_cast(0); + + static FNakamaWriteTournamentRecordRequest_TournamentRecordWrite FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A request to submit a score to a tournament. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaWriteTournamentRecordRequest +{ + GENERATED_BODY() + /** The tournament ID to write the record for. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString TournamentId; + /** Record input. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite Record; + + static FNakamaWriteTournamentRecordRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A request to list parties. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaListPartiesRequest +{ + GENERATED_BODY() + /** Limit the number of returned parties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Limit = 0; + /** Optionally filter by open/closed parties. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Arbitrary label query. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Query; + /** Cursor for the next page of results, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaListPartiesRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Incoming information about a party. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaParty +{ + GENERATED_BODY() + /** Unique party identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString PartyId; + /** Open flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Open = false; + /** Hidden flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + bool Hidden = false; + /** Maximum number of party members. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 MaxSize = 0; + /** The party label, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Label; + + static FNakamaParty FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A list of realtime matches. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaPartyList +{ + GENERATED_BODY() + /** A number of parties corresponding to a list operation. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + TArray Parties; + /** A cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Cursor; + + static FNakamaPartyList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +enum class ENakamaRequestAuth : uint8 +{ + None, + Basic, + Bearer, + HttpKey +}; + +/** Low-level API client configuration. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaClientConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + FString ServerKey; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + FString Host; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + int32 Port = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + bool bUseSSL = false; + + FString GetBaseUrl() const noexcept; +}; + diff --git a/Nakama/Source/NakamaBlueprints/NakamaBlueprints.Build.cs b/Nakama/Source/NakamaBlueprints/NakamaBlueprints.Build.cs index 01ce700c2..1c8c199a6 100644 --- a/Nakama/Source/NakamaBlueprints/NakamaBlueprints.Build.cs +++ b/Nakama/Source/NakamaBlueprints/NakamaBlueprints.Build.cs @@ -40,7 +40,8 @@ public NakamaBlueprints(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", - "NakamaUnreal" + "Nakama", + "NakamaApi", // ... add other public dependencies that you statically link with here ... } ); diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprints.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprints.cpp deleted file mode 100644 index 30fc75bc5..000000000 --- a/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprints.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaBlueprints.h" -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE "FNakamaBlueprintsModule" - -DEFINE_LOG_CATEGORY(LogNakamaBlueprints); - - -void FNakamaBlueprintsModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - - //NLogger::init(std::make_shared(), NLogLevel::Debug); - -} - -void FNakamaBlueprintsModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FNakamaBlueprintsModule, NakamaBlueprints) diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprintsModule.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprintsModule.cpp new file mode 100644 index 000000000..09c24ef26 --- /dev/null +++ b/Nakama/Source/NakamaBlueprints/Private/NakamaBlueprintsModule.cpp @@ -0,0 +1,7 @@ +#include "Modules/ModuleManager.h" + +class FNakamaBlueprintsModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FNakamaBlueprintsModule, NakamaBlueprints) diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaClientBlueprintLibrary.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaClientBlueprintLibrary.cpp new file mode 100644 index 000000000..66b6238c3 --- /dev/null +++ b/Nakama/Source/NakamaBlueprints/Private/NakamaClientBlueprintLibrary.cpp @@ -0,0 +1,4309 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ + +// AddFriends +UNakamaClientAddFriends* UNakamaClientAddFriends::AddFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata) +{ + UNakamaClientAddFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredIds = Ids; + Action->StoredUsernames = Usernames; + Action->StoredMetadata = Metadata; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAddFriends::Activate() +{ + static const TCHAR* TraceScope_AddFriends = TEXT("NakamaBP_AddFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AddFriends); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::AddFriends( + Client, + Session, + StoredIds, + StoredUsernames, + StoredMetadata, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AddGroupUsers +UNakamaClientAddGroupUsers* UNakamaClientAddGroupUsers::AddGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds) +{ + UNakamaClientAddGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAddGroupUsers::Activate() +{ + static const TCHAR* TraceScope_AddGroupUsers = TEXT("NakamaBP_AddGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AddGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::AddGroupUsers( + Client, + Session, + StoredGroupId, + StoredUserIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// SessionRefresh +UNakamaClientSessionRefresh* UNakamaClientSessionRefresh::SessionRefresh( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + const TMap& Vars) +{ + UNakamaClientSessionRefresh* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientSessionRefresh::Activate() +{ + static const TCHAR* TraceScope_SessionRefresh = TEXT("NakamaBP_SessionRefresh"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_SessionRefresh); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::SessionRefresh( + Client, + StoredToken, + StoredVars, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// SessionLogout +UNakamaClientSessionLogout* UNakamaClientSessionLogout::SessionLogout( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + FString RefreshToken) +{ + UNakamaClientSessionLogout* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredRefreshToken = RefreshToken; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientSessionLogout::Activate() +{ + static const TCHAR* TraceScope_SessionLogout = TEXT("NakamaBP_SessionLogout"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_SessionLogout); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::SessionLogout( + Client, + Session, + StoredToken, + StoredRefreshToken, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateApple +UNakamaClientAuthenticateApple* UNakamaClientAuthenticateApple::AuthenticateApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateApple* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateApple::Activate() +{ + static const TCHAR* TraceScope_AuthenticateApple = TEXT("NakamaBP_AuthenticateApple"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateApple); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountApple StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateApple( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateCustom +UNakamaClientAuthenticateCustom* UNakamaClientAuthenticateCustom::AuthenticateCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Id, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateCustom* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateCustom::Activate() +{ + static const TCHAR* TraceScope_AuthenticateCustom = TEXT("NakamaBP_AuthenticateCustom"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateCustom); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountCustom StoredAccount; + StoredAccount.Id = StoredId; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateCustom( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateDevice +UNakamaClientAuthenticateDevice* UNakamaClientAuthenticateDevice::AuthenticateDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Id, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateDevice* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateDevice::Activate() +{ + static const TCHAR* TraceScope_AuthenticateDevice = TEXT("NakamaBP_AuthenticateDevice"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateDevice); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountDevice StoredAccount; + StoredAccount.Id = StoredId; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateDevice( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateEmail +UNakamaClientAuthenticateEmail* UNakamaClientAuthenticateEmail::AuthenticateEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Email, + FString Password, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateEmail* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredEmail = Email; + Action->StoredPassword = Password; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateEmail::Activate() +{ + static const TCHAR* TraceScope_AuthenticateEmail = TEXT("NakamaBP_AuthenticateEmail"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateEmail); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountEmail StoredAccount; + StoredAccount.Email = StoredEmail; + StoredAccount.Password = StoredPassword; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateEmail( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateFacebook +UNakamaClientAuthenticateFacebook* UNakamaClientAuthenticateFacebook::AuthenticateFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars) +{ + UNakamaClientAuthenticateFacebook* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->StoredSync = Sync; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateFacebook::Activate() +{ + static const TCHAR* TraceScope_AuthenticateFacebook = TEXT("NakamaBP_AuthenticateFacebook"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateFacebook); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountFacebook StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateFacebook( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + StoredSync, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateFacebookInstantGame +UNakamaClientAuthenticateFacebookInstantGame* UNakamaClientAuthenticateFacebookInstantGame::AuthenticateFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString SignedPlayerInfo, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateFacebookInstantGame* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredSignedPlayerInfo = SignedPlayerInfo; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateFacebookInstantGame::Activate() +{ + static const TCHAR* TraceScope_AuthenticateFacebookInstantGame = TEXT("NakamaBP_AuthenticateFacebookInstantGame"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateFacebookInstantGame); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountFacebookInstantGame StoredAccount; + StoredAccount.SignedPlayerInfo = StoredSignedPlayerInfo; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateFacebookInstantGame( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateGameCenter +UNakamaClientAuthenticateGameCenter* UNakamaClientAuthenticateGameCenter::AuthenticateGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateGameCenter* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredPlayerId = PlayerId; + Action->StoredBundleId = BundleId; + Action->StoredTimestampSeconds = TimestampSeconds; + Action->StoredSalt = Salt; + Action->StoredSignature = Signature; + Action->StoredPublicKeyUrl = PublicKeyUrl; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateGameCenter::Activate() +{ + static const TCHAR* TraceScope_AuthenticateGameCenter = TEXT("NakamaBP_AuthenticateGameCenter"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateGameCenter); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountGameCenter StoredAccount; + StoredAccount.PlayerId = StoredPlayerId; + StoredAccount.BundleId = StoredBundleId; + StoredAccount.TimestampSeconds = StoredTimestampSeconds; + StoredAccount.Salt = StoredSalt; + StoredAccount.Signature = StoredSignature; + StoredAccount.PublicKeyUrl = StoredPublicKeyUrl; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateGameCenter( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateGoogle +UNakamaClientAuthenticateGoogle* UNakamaClientAuthenticateGoogle::AuthenticateGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + const TMap& Vars) +{ + UNakamaClientAuthenticateGoogle* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateGoogle::Activate() +{ + static const TCHAR* TraceScope_AuthenticateGoogle = TEXT("NakamaBP_AuthenticateGoogle"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateGoogle); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountGoogle StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateGoogle( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateSteam +UNakamaClientAuthenticateSteam* UNakamaClientAuthenticateSteam::AuthenticateSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars) +{ + UNakamaClientAuthenticateSteam* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredCreate = Create; + Action->StoredUsername = Username; + Action->StoredSync = Sync; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientAuthenticateSteam::Activate() +{ + static const TCHAR* TraceScope_AuthenticateSteam = TEXT("NakamaBP_AuthenticateSteam"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateSteam); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountSteam StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::AuthenticateSteam( + Client, + StoredAccount, + StoredCreate, + StoredUsername, + StoredSync, + [WeakThis](const FNakamaSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// BanGroupUsers +UNakamaClientBanGroupUsers* UNakamaClientBanGroupUsers::BanGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds) +{ + UNakamaClientBanGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientBanGroupUsers::Activate() +{ + static const TCHAR* TraceScope_BanGroupUsers = TEXT("NakamaBP_BanGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_BanGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::BanGroupUsers( + Client, + Session, + StoredGroupId, + StoredUserIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// BlockFriends +UNakamaClientBlockFriends* UNakamaClientBlockFriends::BlockFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames) +{ + UNakamaClientBlockFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredIds = Ids; + Action->StoredUsernames = Usernames; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientBlockFriends::Activate() +{ + static const TCHAR* TraceScope_BlockFriends = TEXT("NakamaBP_BlockFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_BlockFriends); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::BlockFriends( + Client, + Session, + StoredIds, + StoredUsernames, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// CreateGroup +UNakamaClientCreateGroup* UNakamaClientCreateGroup::CreateGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount) +{ + UNakamaClientCreateGroup* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredName = Name; + Action->StoredDescription = Description; + Action->StoredLangTag = LangTag; + Action->StoredAvatarUrl = AvatarUrl; + Action->StoredOpen = Open; + Action->StoredMaxCount = MaxCount; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientCreateGroup::Activate() +{ + static const TCHAR* TraceScope_CreateGroup = TEXT("NakamaBP_CreateGroup"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_CreateGroup); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::CreateGroup( + Client, + Session, + StoredName, + StoredDescription, + StoredLangTag, + StoredAvatarUrl, + StoredOpen, + StoredMaxCount, + [WeakThis](const FNakamaGroup& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteAccount +UNakamaClientDeleteAccount* UNakamaClientDeleteAccount::DeleteAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session) +{ + UNakamaClientDeleteAccount* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteAccount::Activate() +{ + static const TCHAR* TraceScope_DeleteAccount = TEXT("NakamaBP_DeleteAccount"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteAccount); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteAccount( + Client, + Session, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteFriends +UNakamaClientDeleteFriends* UNakamaClientDeleteFriends::DeleteFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames) +{ + UNakamaClientDeleteFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredIds = Ids; + Action->StoredUsernames = Usernames; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteFriends::Activate() +{ + static const TCHAR* TraceScope_DeleteFriends = TEXT("NakamaBP_DeleteFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteFriends); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteFriends( + Client, + Session, + StoredIds, + StoredUsernames, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteGroup +UNakamaClientDeleteGroup* UNakamaClientDeleteGroup::DeleteGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId) +{ + UNakamaClientDeleteGroup* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteGroup::Activate() +{ + static const TCHAR* TraceScope_DeleteGroup = TEXT("NakamaBP_DeleteGroup"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteGroup); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteGroup( + Client, + Session, + StoredGroupId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteLeaderboardRecord +UNakamaClientDeleteLeaderboardRecord* UNakamaClientDeleteLeaderboardRecord::DeleteLeaderboardRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId) +{ + UNakamaClientDeleteLeaderboardRecord* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLeaderboardId = LeaderboardId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteLeaderboardRecord::Activate() +{ + static const TCHAR* TraceScope_DeleteLeaderboardRecord = TEXT("NakamaBP_DeleteLeaderboardRecord"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteLeaderboardRecord); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteLeaderboardRecord( + Client, + Session, + StoredLeaderboardId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteNotifications +UNakamaClientDeleteNotifications* UNakamaClientDeleteNotifications::DeleteNotifications( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids) +{ + UNakamaClientDeleteNotifications* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredIds = Ids; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteNotifications::Activate() +{ + static const TCHAR* TraceScope_DeleteNotifications = TEXT("NakamaBP_DeleteNotifications"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteNotifications); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteNotifications( + Client, + Session, + StoredIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteTournamentRecord +UNakamaClientDeleteTournamentRecord* UNakamaClientDeleteTournamentRecord::DeleteTournamentRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId) +{ + UNakamaClientDeleteTournamentRecord* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredTournamentId = TournamentId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteTournamentRecord::Activate() +{ + static const TCHAR* TraceScope_DeleteTournamentRecord = TEXT("NakamaBP_DeleteTournamentRecord"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteTournamentRecord); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteTournamentRecord( + Client, + Session, + StoredTournamentId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteStorageObjects +UNakamaClientDeleteStorageObjects* UNakamaClientDeleteStorageObjects::DeleteStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& ObjectIds) +{ + UNakamaClientDeleteStorageObjects* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredObjectIds = ObjectIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDeleteStorageObjects::Activate() +{ + static const TCHAR* TraceScope_DeleteStorageObjects = TEXT("NakamaBP_DeleteStorageObjects"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteStorageObjects); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DeleteStorageObjects( + Client, + Session, + StoredObjectIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Event +UNakamaClientEvent* UNakamaClientEvent::Event( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties) +{ + UNakamaClientEvent* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredName = Name; + Action->StoredTimestamp = Timestamp; + Action->StoredExternal = External; + Action->StoredProperties = Properties; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientEvent::Activate() +{ + static const TCHAR* TraceScope_Event = TEXT("NakamaBP_Event"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Event); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::Event( + Client, + Session, + StoredName, + StoredTimestamp, + StoredExternal, + StoredProperties, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetAccount +UNakamaClientGetAccount* UNakamaClientGetAccount::GetAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session) +{ + UNakamaClientGetAccount* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientGetAccount::Activate() +{ + static const TCHAR* TraceScope_GetAccount = TEXT("NakamaBP_GetAccount"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetAccount); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::GetAccount( + Client, + Session, + [WeakThis](const FNakamaAccount& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetUsers +UNakamaClientGetUsers* UNakamaClientGetUsers::GetUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds) +{ + UNakamaClientGetUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredIds = Ids; + Action->StoredUsernames = Usernames; + Action->StoredFacebookIds = FacebookIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientGetUsers::Activate() +{ + static const TCHAR* TraceScope_GetUsers = TEXT("NakamaBP_GetUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::GetUsers( + Client, + Session, + StoredIds, + StoredUsernames, + StoredFacebookIds, + [WeakThis](const FNakamaUsers& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetSubscription +UNakamaClientGetSubscription* UNakamaClientGetSubscription::GetSubscription( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString ProductId) +{ + UNakamaClientGetSubscription* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredProductId = ProductId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientGetSubscription::Activate() +{ + static const TCHAR* TraceScope_GetSubscription = TEXT("NakamaBP_GetSubscription"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetSubscription); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::GetSubscription( + Client, + Session, + StoredProductId, + [WeakThis](const FNakamaValidatedSubscription& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetMatchmakerStats +UNakamaClientGetMatchmakerStats* UNakamaClientGetMatchmakerStats::GetMatchmakerStats( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session) +{ + UNakamaClientGetMatchmakerStats* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientGetMatchmakerStats::Activate() +{ + static const TCHAR* TraceScope_GetMatchmakerStats = TEXT("NakamaBP_GetMatchmakerStats"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetMatchmakerStats); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::GetMatchmakerStats( + Client, + Session, + [WeakThis](const FNakamaMatchmakerStats& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Healthcheck +UNakamaClientHealthcheck* UNakamaClientHealthcheck::Healthcheck( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session) +{ + UNakamaClientHealthcheck* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientHealthcheck::Activate() +{ + static const TCHAR* TraceScope_Healthcheck = TEXT("NakamaBP_Healthcheck"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Healthcheck); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::Healthcheck( + Client, + Session, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ImportFacebookFriends +UNakamaClientImportFacebookFriends* UNakamaClientImportFacebookFriends::ImportFacebookFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars) +{ + UNakamaClientImportFacebookFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredReset = Reset; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientImportFacebookFriends::Activate() +{ + static const TCHAR* TraceScope_ImportFacebookFriends = TEXT("NakamaBP_ImportFacebookFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ImportFacebookFriends); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountFacebook StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::ImportFacebookFriends( + Client, + Session, + StoredAccount, + StoredReset, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ImportSteamFriends +UNakamaClientImportSteamFriends* UNakamaClientImportSteamFriends::ImportSteamFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars) +{ + UNakamaClientImportSteamFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredReset = Reset; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientImportSteamFriends::Activate() +{ + static const TCHAR* TraceScope_ImportSteamFriends = TEXT("NakamaBP_ImportSteamFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ImportSteamFriends); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountSteam StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::ImportSteamFriends( + Client, + Session, + StoredAccount, + StoredReset, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// JoinGroup +UNakamaClientJoinGroup* UNakamaClientJoinGroup::JoinGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId) +{ + UNakamaClientJoinGroup* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientJoinGroup::Activate() +{ + static const TCHAR* TraceScope_JoinGroup = TEXT("NakamaBP_JoinGroup"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_JoinGroup); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::JoinGroup( + Client, + Session, + StoredGroupId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// JoinTournament +UNakamaClientJoinTournament* UNakamaClientJoinTournament::JoinTournament( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId) +{ + UNakamaClientJoinTournament* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredTournamentId = TournamentId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientJoinTournament::Activate() +{ + static const TCHAR* TraceScope_JoinTournament = TEXT("NakamaBP_JoinTournament"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_JoinTournament); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::JoinTournament( + Client, + Session, + StoredTournamentId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// KickGroupUsers +UNakamaClientKickGroupUsers* UNakamaClientKickGroupUsers::KickGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds) +{ + UNakamaClientKickGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientKickGroupUsers::Activate() +{ + static const TCHAR* TraceScope_KickGroupUsers = TEXT("NakamaBP_KickGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_KickGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::KickGroupUsers( + Client, + Session, + StoredGroupId, + StoredUserIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LeaveGroup +UNakamaClientLeaveGroup* UNakamaClientLeaveGroup::LeaveGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId) +{ + UNakamaClientLeaveGroup* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLeaveGroup::Activate() +{ + static const TCHAR* TraceScope_LeaveGroup = TEXT("NakamaBP_LeaveGroup"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LeaveGroup); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LeaveGroup( + Client, + Session, + StoredGroupId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkApple +UNakamaClientLinkApple* UNakamaClientLinkApple::LinkApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientLinkApple* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkApple::Activate() +{ + static const TCHAR* TraceScope_LinkApple = TEXT("NakamaBP_LinkApple"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkApple); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkApple( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkCustom +UNakamaClientLinkCustom* UNakamaClientLinkCustom::LinkCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars) +{ + UNakamaClientLinkCustom* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkCustom::Activate() +{ + static const TCHAR* TraceScope_LinkCustom = TEXT("NakamaBP_LinkCustom"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkCustom); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkCustom( + Client, + Session, + StoredId, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkDevice +UNakamaClientLinkDevice* UNakamaClientLinkDevice::LinkDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars) +{ + UNakamaClientLinkDevice* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkDevice::Activate() +{ + static const TCHAR* TraceScope_LinkDevice = TEXT("NakamaBP_LinkDevice"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkDevice); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkDevice( + Client, + Session, + StoredId, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkEmail +UNakamaClientLinkEmail* UNakamaClientLinkEmail::LinkEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars) +{ + UNakamaClientLinkEmail* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredEmail = Email; + Action->StoredPassword = Password; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkEmail::Activate() +{ + static const TCHAR* TraceScope_LinkEmail = TEXT("NakamaBP_LinkEmail"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkEmail); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkEmail( + Client, + Session, + StoredEmail, + StoredPassword, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkFacebook +UNakamaClientLinkFacebook* UNakamaClientLinkFacebook::LinkFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars) +{ + UNakamaClientLinkFacebook* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredSync = Sync; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkFacebook::Activate() +{ + static const TCHAR* TraceScope_LinkFacebook = TEXT("NakamaBP_LinkFacebook"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkFacebook); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountFacebook StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::LinkFacebook( + Client, + Session, + StoredAccount, + StoredSync, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkFacebookInstantGame +UNakamaClientLinkFacebookInstantGame* UNakamaClientLinkFacebookInstantGame::LinkFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars) +{ + UNakamaClientLinkFacebookInstantGame* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredSignedPlayerInfo = SignedPlayerInfo; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkFacebookInstantGame::Activate() +{ + static const TCHAR* TraceScope_LinkFacebookInstantGame = TEXT("NakamaBP_LinkFacebookInstantGame"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkFacebookInstantGame); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkFacebookInstantGame( + Client, + Session, + StoredSignedPlayerInfo, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkGameCenter +UNakamaClientLinkGameCenter* UNakamaClientLinkGameCenter::LinkGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars) +{ + UNakamaClientLinkGameCenter* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredPlayerId = PlayerId; + Action->StoredBundleId = BundleId; + Action->StoredTimestampSeconds = TimestampSeconds; + Action->StoredSalt = Salt; + Action->StoredSignature = Signature; + Action->StoredPublicKeyUrl = PublicKeyUrl; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkGameCenter::Activate() +{ + static const TCHAR* TraceScope_LinkGameCenter = TEXT("NakamaBP_LinkGameCenter"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkGameCenter); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkGameCenter( + Client, + Session, + StoredPlayerId, + StoredBundleId, + StoredTimestampSeconds, + StoredSalt, + StoredSignature, + StoredPublicKeyUrl, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkGoogle +UNakamaClientLinkGoogle* UNakamaClientLinkGoogle::LinkGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientLinkGoogle* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkGoogle::Activate() +{ + static const TCHAR* TraceScope_LinkGoogle = TEXT("NakamaBP_LinkGoogle"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkGoogle); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::LinkGoogle( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// LinkSteam +UNakamaClientLinkSteam* UNakamaClientLinkSteam::LinkSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars) +{ + UNakamaClientLinkSteam* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->StoredSync = Sync; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientLinkSteam::Activate() +{ + static const TCHAR* TraceScope_LinkSteam = TEXT("NakamaBP_LinkSteam"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_LinkSteam); + + TWeakObjectPtr WeakThis(this); + FNakamaAccountSteam StoredAccount; + StoredAccount.Token = StoredToken; + StoredAccount.Vars = StoredVars; + + NakamaApi::LinkSteam( + Client, + Session, + StoredAccount, + StoredSync, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListChannelMessages +UNakamaClientListChannelMessages* UNakamaClientListChannelMessages::ListChannelMessages( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor) +{ + UNakamaClientListChannelMessages* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredChannelId = ChannelId; + Action->StoredLimit = Limit; + Action->StoredForward = Forward; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListChannelMessages::Activate() +{ + static const TCHAR* TraceScope_ListChannelMessages = TEXT("NakamaBP_ListChannelMessages"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListChannelMessages); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListChannelMessages( + Client, + Session, + StoredChannelId, + StoredLimit, + StoredForward, + StoredCursor, + [WeakThis](const FNakamaChannelMessageList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListFriends +UNakamaClientListFriends* UNakamaClientListFriends::ListFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor) +{ + UNakamaClientListFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredState = State; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListFriends::Activate() +{ + static const TCHAR* TraceScope_ListFriends = TEXT("NakamaBP_ListFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListFriends); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListFriends( + Client, + Session, + StoredLimit, + StoredState, + StoredCursor, + [WeakThis](const FNakamaFriendList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListFriendsOfFriends +UNakamaClientListFriendsOfFriends* UNakamaClientListFriendsOfFriends::ListFriendsOfFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString Cursor) +{ + UNakamaClientListFriendsOfFriends* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListFriendsOfFriends::Activate() +{ + static const TCHAR* TraceScope_ListFriendsOfFriends = TEXT("NakamaBP_ListFriendsOfFriends"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListFriendsOfFriends); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListFriendsOfFriends( + Client, + Session, + StoredLimit, + StoredCursor, + [WeakThis](const FNakamaFriendsOfFriendsList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListGroups +UNakamaClientListGroups* UNakamaClientListGroups::ListGroups( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open) +{ + UNakamaClientListGroups* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredName = Name; + Action->StoredCursor = Cursor; + Action->StoredLimit = Limit; + Action->StoredLangTag = LangTag; + Action->StoredMembers = Members; + Action->StoredOpen = Open; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListGroups::Activate() +{ + static const TCHAR* TraceScope_ListGroups = TEXT("NakamaBP_ListGroups"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListGroups); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListGroups( + Client, + Session, + StoredName, + StoredCursor, + StoredLimit, + StoredLangTag, + StoredMembers, + StoredOpen, + [WeakThis](const FNakamaGroupList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListGroupUsers +UNakamaClientListGroupUsers* UNakamaClientListGroupUsers::ListGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor) +{ + UNakamaClientListGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredLimit = Limit; + Action->StoredState = State; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListGroupUsers::Activate() +{ + static const TCHAR* TraceScope_ListGroupUsers = TEXT("NakamaBP_ListGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListGroupUsers( + Client, + Session, + StoredGroupId, + StoredLimit, + StoredState, + StoredCursor, + [WeakThis](const FNakamaGroupUserList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListLeaderboardRecords +UNakamaClientListLeaderboardRecords* UNakamaClientListLeaderboardRecords::ListLeaderboardRecords( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry) +{ + UNakamaClientListLeaderboardRecords* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLeaderboardId = LeaderboardId; + Action->StoredOwnerIds = OwnerIds; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->StoredExpiry = Expiry; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListLeaderboardRecords::Activate() +{ + static const TCHAR* TraceScope_ListLeaderboardRecords = TEXT("NakamaBP_ListLeaderboardRecords"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListLeaderboardRecords); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListLeaderboardRecords( + Client, + Session, + StoredLeaderboardId, + StoredOwnerIds, + StoredLimit, + StoredCursor, + StoredExpiry, + [WeakThis](const FNakamaLeaderboardRecordList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListLeaderboardRecordsAroundOwner +UNakamaClientListLeaderboardRecordsAroundOwner* UNakamaClientListLeaderboardRecordsAroundOwner::ListLeaderboardRecordsAroundOwner( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor) +{ + UNakamaClientListLeaderboardRecordsAroundOwner* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLeaderboardId = LeaderboardId; + Action->StoredLimit = Limit; + Action->StoredOwnerId = OwnerId; + Action->StoredExpiry = Expiry; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListLeaderboardRecordsAroundOwner::Activate() +{ + static const TCHAR* TraceScope_ListLeaderboardRecordsAroundOwner = TEXT("NakamaBP_ListLeaderboardRecordsAroundOwner"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListLeaderboardRecordsAroundOwner); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListLeaderboardRecordsAroundOwner( + Client, + Session, + StoredLeaderboardId, + StoredLimit, + StoredOwnerId, + StoredExpiry, + StoredCursor, + [WeakThis](const FNakamaLeaderboardRecordList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListMatches +UNakamaClientListMatches* UNakamaClientListMatches::ListMatches( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query) +{ + UNakamaClientListMatches* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredAuthoritative = Authoritative; + Action->StoredLabel = Label; + Action->StoredMinSize = MinSize; + Action->StoredMaxSize = MaxSize; + Action->StoredQuery = Query; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListMatches::Activate() +{ + static const TCHAR* TraceScope_ListMatches = TEXT("NakamaBP_ListMatches"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListMatches); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListMatches( + Client, + Session, + StoredLimit, + StoredAuthoritative, + StoredLabel, + StoredMinSize, + StoredMaxSize, + StoredQuery, + [WeakThis](const FNakamaMatchList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListParties +UNakamaClientListParties* UNakamaClientListParties::ListParties( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor) +{ + UNakamaClientListParties* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredOpen = Open; + Action->StoredQuery = Query; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListParties::Activate() +{ + static const TCHAR* TraceScope_ListParties = TEXT("NakamaBP_ListParties"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListParties); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListParties( + Client, + Session, + StoredLimit, + StoredOpen, + StoredQuery, + StoredCursor, + [WeakThis](const FNakamaPartyList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListNotifications +UNakamaClientListNotifications* UNakamaClientListNotifications::ListNotifications( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor) +{ + UNakamaClientListNotifications* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredCacheableCursor = CacheableCursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListNotifications::Activate() +{ + static const TCHAR* TraceScope_ListNotifications = TEXT("NakamaBP_ListNotifications"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListNotifications); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListNotifications( + Client, + Session, + StoredLimit, + StoredCacheableCursor, + [WeakThis](const FNakamaNotificationList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListStorageObjects +UNakamaClientListStorageObjects* UNakamaClientListStorageObjects::ListStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor) +{ + UNakamaClientListStorageObjects* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredUserId = UserId; + Action->StoredCollection = Collection; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListStorageObjects::Activate() +{ + static const TCHAR* TraceScope_ListStorageObjects = TEXT("NakamaBP_ListStorageObjects"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListStorageObjects); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListStorageObjects( + Client, + Session, + StoredUserId, + StoredCollection, + StoredLimit, + StoredCursor, + [WeakThis](const FNakamaStorageObjectList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListSubscriptions +UNakamaClientListSubscriptions* UNakamaClientListSubscriptions::ListSubscriptions( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString Cursor) +{ + UNakamaClientListSubscriptions* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListSubscriptions::Activate() +{ + static const TCHAR* TraceScope_ListSubscriptions = TEXT("NakamaBP_ListSubscriptions"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListSubscriptions); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListSubscriptions( + Client, + Session, + StoredLimit, + StoredCursor, + [WeakThis](const FNakamaSubscriptionList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListTournaments +UNakamaClientListTournaments* UNakamaClientListTournaments::ListTournaments( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor) +{ + UNakamaClientListTournaments* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredCategoryStart = CategoryStart; + Action->StoredCategoryEnd = CategoryEnd; + Action->StoredStartTime = StartTime; + Action->StoredEndTime = EndTime; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListTournaments::Activate() +{ + static const TCHAR* TraceScope_ListTournaments = TEXT("NakamaBP_ListTournaments"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListTournaments); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListTournaments( + Client, + Session, + StoredCategoryStart, + StoredCategoryEnd, + StoredStartTime, + StoredEndTime, + StoredLimit, + StoredCursor, + [WeakThis](const FNakamaTournamentList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListTournamentRecords +UNakamaClientListTournamentRecords* UNakamaClientListTournamentRecords::ListTournamentRecords( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry) +{ + UNakamaClientListTournamentRecords* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredTournamentId = TournamentId; + Action->StoredOwnerIds = OwnerIds; + Action->StoredLimit = Limit; + Action->StoredCursor = Cursor; + Action->StoredExpiry = Expiry; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListTournamentRecords::Activate() +{ + static const TCHAR* TraceScope_ListTournamentRecords = TEXT("NakamaBP_ListTournamentRecords"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListTournamentRecords); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListTournamentRecords( + Client, + Session, + StoredTournamentId, + StoredOwnerIds, + StoredLimit, + StoredCursor, + StoredExpiry, + [WeakThis](const FNakamaTournamentRecordList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListTournamentRecordsAroundOwner +UNakamaClientListTournamentRecordsAroundOwner* UNakamaClientListTournamentRecordsAroundOwner::ListTournamentRecordsAroundOwner( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor) +{ + UNakamaClientListTournamentRecordsAroundOwner* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredTournamentId = TournamentId; + Action->StoredLimit = Limit; + Action->StoredOwnerId = OwnerId; + Action->StoredExpiry = Expiry; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListTournamentRecordsAroundOwner::Activate() +{ + static const TCHAR* TraceScope_ListTournamentRecordsAroundOwner = TEXT("NakamaBP_ListTournamentRecordsAroundOwner"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListTournamentRecordsAroundOwner); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListTournamentRecordsAroundOwner( + Client, + Session, + StoredTournamentId, + StoredLimit, + StoredOwnerId, + StoredExpiry, + StoredCursor, + [WeakThis](const FNakamaTournamentRecordList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListUserGroups +UNakamaClientListUserGroups* UNakamaClientListUserGroups::ListUserGroups( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor) +{ + UNakamaClientListUserGroups* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredUserId = UserId; + Action->StoredLimit = Limit; + Action->StoredState = State; + Action->StoredCursor = Cursor; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientListUserGroups::Activate() +{ + static const TCHAR* TraceScope_ListUserGroups = TEXT("NakamaBP_ListUserGroups"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListUserGroups); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ListUserGroups( + Client, + Session, + StoredUserId, + StoredLimit, + StoredState, + StoredCursor, + [WeakThis](const FNakamaUserGroupList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// PromoteGroupUsers +UNakamaClientPromoteGroupUsers* UNakamaClientPromoteGroupUsers::PromoteGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds) +{ + UNakamaClientPromoteGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientPromoteGroupUsers::Activate() +{ + static const TCHAR* TraceScope_PromoteGroupUsers = TEXT("NakamaBP_PromoteGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PromoteGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::PromoteGroupUsers( + Client, + Session, + StoredGroupId, + StoredUserIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DemoteGroupUsers +UNakamaClientDemoteGroupUsers* UNakamaClientDemoteGroupUsers::DemoteGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds) +{ + UNakamaClientDemoteGroupUsers* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientDemoteGroupUsers::Activate() +{ + static const TCHAR* TraceScope_DemoteGroupUsers = TEXT("NakamaBP_DemoteGroupUsers"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DemoteGroupUsers); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::DemoteGroupUsers( + Client, + Session, + StoredGroupId, + StoredUserIds, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ReadStorageObjects +UNakamaClientReadStorageObjects* UNakamaClientReadStorageObjects::ReadStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& ObjectIds) +{ + UNakamaClientReadStorageObjects* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredObjectIds = ObjectIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientReadStorageObjects::Activate() +{ + static const TCHAR* TraceScope_ReadStorageObjects = TEXT("NakamaBP_ReadStorageObjects"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ReadStorageObjects); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ReadStorageObjects( + Client, + Session, + StoredObjectIds, + [WeakThis](const FNakamaStorageObjects& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// RpcFunc +UNakamaClientRpcFunc* UNakamaClientRpcFunc::RpcFunc( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Payload, + FString HttpKey) +{ + UNakamaClientRpcFunc* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredPayload = Payload; + Action->StoredHttpKey = HttpKey; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientRpcFunc::Activate() +{ + static const TCHAR* TraceScope_RpcFunc = TEXT("NakamaBP_RpcFunc"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_RpcFunc); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::RpcFunc( + Client, + Session, + StoredId, + [&]() -> TSharedPtr { + if (StoredPayload.Num() == 0) { return nullptr; } + TSharedPtr Json = MakeShared(); + for (const auto& Pair : StoredPayload) + { + Json->SetStringField(Pair.Key, Pair.Value); + } + return Json; + }(), + StoredHttpKey, + [WeakThis](const FNakamaRpc& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkApple +UNakamaClientUnlinkApple* UNakamaClientUnlinkApple::UnlinkApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientUnlinkApple* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkApple::Activate() +{ + static const TCHAR* TraceScope_UnlinkApple = TEXT("NakamaBP_UnlinkApple"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkApple); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkApple( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkCustom +UNakamaClientUnlinkCustom* UNakamaClientUnlinkCustom::UnlinkCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars) +{ + UNakamaClientUnlinkCustom* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkCustom::Activate() +{ + static const TCHAR* TraceScope_UnlinkCustom = TEXT("NakamaBP_UnlinkCustom"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkCustom); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkCustom( + Client, + Session, + StoredId, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkDevice +UNakamaClientUnlinkDevice* UNakamaClientUnlinkDevice::UnlinkDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars) +{ + UNakamaClientUnlinkDevice* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkDevice::Activate() +{ + static const TCHAR* TraceScope_UnlinkDevice = TEXT("NakamaBP_UnlinkDevice"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkDevice); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkDevice( + Client, + Session, + StoredId, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkEmail +UNakamaClientUnlinkEmail* UNakamaClientUnlinkEmail::UnlinkEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars) +{ + UNakamaClientUnlinkEmail* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredEmail = Email; + Action->StoredPassword = Password; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkEmail::Activate() +{ + static const TCHAR* TraceScope_UnlinkEmail = TEXT("NakamaBP_UnlinkEmail"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkEmail); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkEmail( + Client, + Session, + StoredEmail, + StoredPassword, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkFacebook +UNakamaClientUnlinkFacebook* UNakamaClientUnlinkFacebook::UnlinkFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientUnlinkFacebook* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkFacebook::Activate() +{ + static const TCHAR* TraceScope_UnlinkFacebook = TEXT("NakamaBP_UnlinkFacebook"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkFacebook); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkFacebook( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkFacebookInstantGame +UNakamaClientUnlinkFacebookInstantGame* UNakamaClientUnlinkFacebookInstantGame::UnlinkFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars) +{ + UNakamaClientUnlinkFacebookInstantGame* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredSignedPlayerInfo = SignedPlayerInfo; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkFacebookInstantGame::Activate() +{ + static const TCHAR* TraceScope_UnlinkFacebookInstantGame = TEXT("NakamaBP_UnlinkFacebookInstantGame"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkFacebookInstantGame); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkFacebookInstantGame( + Client, + Session, + StoredSignedPlayerInfo, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkGameCenter +UNakamaClientUnlinkGameCenter* UNakamaClientUnlinkGameCenter::UnlinkGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars) +{ + UNakamaClientUnlinkGameCenter* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredPlayerId = PlayerId; + Action->StoredBundleId = BundleId; + Action->StoredTimestampSeconds = TimestampSeconds; + Action->StoredSalt = Salt; + Action->StoredSignature = Signature; + Action->StoredPublicKeyUrl = PublicKeyUrl; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkGameCenter::Activate() +{ + static const TCHAR* TraceScope_UnlinkGameCenter = TEXT("NakamaBP_UnlinkGameCenter"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkGameCenter); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkGameCenter( + Client, + Session, + StoredPlayerId, + StoredBundleId, + StoredTimestampSeconds, + StoredSalt, + StoredSignature, + StoredPublicKeyUrl, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkGoogle +UNakamaClientUnlinkGoogle* UNakamaClientUnlinkGoogle::UnlinkGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientUnlinkGoogle* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkGoogle::Activate() +{ + static const TCHAR* TraceScope_UnlinkGoogle = TEXT("NakamaBP_UnlinkGoogle"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkGoogle); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkGoogle( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UnlinkSteam +UNakamaClientUnlinkSteam* UNakamaClientUnlinkSteam::UnlinkSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars) +{ + UNakamaClientUnlinkSteam* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredToken = Token; + Action->StoredVars = Vars; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUnlinkSteam::Activate() +{ + static const TCHAR* TraceScope_UnlinkSteam = TEXT("NakamaBP_UnlinkSteam"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UnlinkSteam); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UnlinkSteam( + Client, + Session, + StoredToken, + StoredVars, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UpdateAccount +UNakamaClientUpdateAccount* UNakamaClientUpdateAccount::UpdateAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone) +{ + UNakamaClientUpdateAccount* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredUsername = Username; + Action->StoredDisplayName = DisplayName; + Action->StoredAvatarUrl = AvatarUrl; + Action->StoredLangTag = LangTag; + Action->StoredLocation = Location; + Action->StoredTimezone = Timezone; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUpdateAccount::Activate() +{ + static const TCHAR* TraceScope_UpdateAccount = TEXT("NakamaBP_UpdateAccount"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UpdateAccount); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UpdateAccount( + Client, + Session, + StoredUsername, + StoredDisplayName, + StoredAvatarUrl, + StoredLangTag, + StoredLocation, + StoredTimezone, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UpdateGroup +UNakamaClientUpdateGroup* UNakamaClientUpdateGroup::UpdateGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open) +{ + UNakamaClientUpdateGroup* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredGroupId = GroupId; + Action->StoredName = Name; + Action->StoredDescription = Description; + Action->StoredLangTag = LangTag; + Action->StoredAvatarUrl = AvatarUrl; + Action->StoredOpen = Open; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientUpdateGroup::Activate() +{ + static const TCHAR* TraceScope_UpdateGroup = TEXT("NakamaBP_UpdateGroup"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UpdateGroup); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::UpdateGroup( + Client, + Session, + StoredGroupId, + StoredName, + StoredDescription, + StoredLangTag, + StoredAvatarUrl, + StoredOpen, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidatePurchaseApple +UNakamaClientValidatePurchaseApple* UNakamaClientValidatePurchaseApple::ValidatePurchaseApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist) +{ + UNakamaClientValidatePurchaseApple* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredReceipt = Receipt; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidatePurchaseApple::Activate() +{ + static const TCHAR* TraceScope_ValidatePurchaseApple = TEXT("NakamaBP_ValidatePurchaseApple"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidatePurchaseApple); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidatePurchaseApple( + Client, + Session, + StoredReceipt, + StoredPersist, + [WeakThis](const FNakamaValidatePurchaseResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidateSubscriptionApple +UNakamaClientValidateSubscriptionApple* UNakamaClientValidateSubscriptionApple::ValidateSubscriptionApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist) +{ + UNakamaClientValidateSubscriptionApple* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredReceipt = Receipt; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidateSubscriptionApple::Activate() +{ + static const TCHAR* TraceScope_ValidateSubscriptionApple = TEXT("NakamaBP_ValidateSubscriptionApple"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidateSubscriptionApple); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidateSubscriptionApple( + Client, + Session, + StoredReceipt, + StoredPersist, + [WeakThis](const FNakamaValidateSubscriptionResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidatePurchaseGoogle +UNakamaClientValidatePurchaseGoogle* UNakamaClientValidatePurchaseGoogle::ValidatePurchaseGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Purchase, + bool Persist) +{ + UNakamaClientValidatePurchaseGoogle* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredPurchase = Purchase; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidatePurchaseGoogle::Activate() +{ + static const TCHAR* TraceScope_ValidatePurchaseGoogle = TEXT("NakamaBP_ValidatePurchaseGoogle"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidatePurchaseGoogle); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidatePurchaseGoogle( + Client, + Session, + StoredPurchase, + StoredPersist, + [WeakThis](const FNakamaValidatePurchaseResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidateSubscriptionGoogle +UNakamaClientValidateSubscriptionGoogle* UNakamaClientValidateSubscriptionGoogle::ValidateSubscriptionGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist) +{ + UNakamaClientValidateSubscriptionGoogle* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredReceipt = Receipt; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidateSubscriptionGoogle::Activate() +{ + static const TCHAR* TraceScope_ValidateSubscriptionGoogle = TEXT("NakamaBP_ValidateSubscriptionGoogle"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidateSubscriptionGoogle); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidateSubscriptionGoogle( + Client, + Session, + StoredReceipt, + StoredPersist, + [WeakThis](const FNakamaValidateSubscriptionResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidatePurchaseHuawei +UNakamaClientValidatePurchaseHuawei* UNakamaClientValidatePurchaseHuawei::ValidatePurchaseHuawei( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist) +{ + UNakamaClientValidatePurchaseHuawei* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredPurchase = Purchase; + Action->StoredSignature = Signature; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidatePurchaseHuawei::Activate() +{ + static const TCHAR* TraceScope_ValidatePurchaseHuawei = TEXT("NakamaBP_ValidatePurchaseHuawei"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidatePurchaseHuawei); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidatePurchaseHuawei( + Client, + Session, + StoredPurchase, + StoredSignature, + StoredPersist, + [WeakThis](const FNakamaValidatePurchaseResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ValidatePurchaseFacebookInstant +UNakamaClientValidatePurchaseFacebookInstant* UNakamaClientValidatePurchaseFacebookInstant::ValidatePurchaseFacebookInstant( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist) +{ + UNakamaClientValidatePurchaseFacebookInstant* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredSignedRequest = SignedRequest; + Action->StoredPersist = Persist; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientValidatePurchaseFacebookInstant::Activate() +{ + static const TCHAR* TraceScope_ValidatePurchaseFacebookInstant = TEXT("NakamaBP_ValidatePurchaseFacebookInstant"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ValidatePurchaseFacebookInstant); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::ValidatePurchaseFacebookInstant( + Client, + Session, + StoredSignedRequest, + StoredPersist, + [WeakThis](const FNakamaValidatePurchaseResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// WriteLeaderboardRecord +UNakamaClientWriteLeaderboardRecord* UNakamaClientWriteLeaderboardRecord::WriteLeaderboardRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator) +{ + UNakamaClientWriteLeaderboardRecord* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLeaderboardId = LeaderboardId; + Action->StoredScore = Score; + Action->StoredSubscore = Subscore; + Action->StoredMetadata = Metadata; + Action->StoredOperator = Operator; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientWriteLeaderboardRecord::Activate() +{ + static const TCHAR* TraceScope_WriteLeaderboardRecord = TEXT("NakamaBP_WriteLeaderboardRecord"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_WriteLeaderboardRecord); + + TWeakObjectPtr WeakThis(this); + FNakamaWriteLeaderboardRecordRequest_LeaderboardRecordWrite StoredRecord; + StoredRecord.Score = StoredScore; + StoredRecord.Subscore = StoredSubscore; + StoredRecord.Metadata = StoredMetadata; + StoredRecord.Operator = StoredOperator; + + NakamaApi::WriteLeaderboardRecord( + Client, + Session, + StoredLeaderboardId, + StoredRecord, + [WeakThis](const FNakamaLeaderboardRecord& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// WriteStorageObjects +UNakamaClientWriteStorageObjects* UNakamaClientWriteStorageObjects::WriteStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Objects) +{ + UNakamaClientWriteStorageObjects* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredObjects = Objects; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientWriteStorageObjects::Activate() +{ + static const TCHAR* TraceScope_WriteStorageObjects = TEXT("NakamaBP_WriteStorageObjects"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_WriteStorageObjects); + + TWeakObjectPtr WeakThis(this); + + NakamaApi::WriteStorageObjects( + Client, + Session, + StoredObjects, + [WeakThis](const FNakamaStorageObjectAcks& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// WriteTournamentRecord +UNakamaClientWriteTournamentRecord* UNakamaClientWriteTournamentRecord::WriteTournamentRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator) +{ + UNakamaClientWriteTournamentRecord* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredTournamentId = TournamentId; + Action->StoredScore = Score; + Action->StoredSubscore = Subscore; + Action->StoredMetadata = Metadata; + Action->StoredOperator = Operator; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClientWriteTournamentRecord::Activate() +{ + static const TCHAR* TraceScope_WriteTournamentRecord = TEXT("NakamaBP_WriteTournamentRecord"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_WriteTournamentRecord); + + TWeakObjectPtr WeakThis(this); + FNakamaWriteTournamentRecordRequest_TournamentRecordWrite StoredRecord; + StoredRecord.Score = StoredScore; + StoredRecord.Subscore = StoredSubscore; + StoredRecord.Metadata = StoredMetadata; + StoredRecord.Operator = StoredOperator; + + NakamaApi::WriteTournamentRecord( + Client, + Session, + StoredTournamentId, + StoredRecord, + [WeakThis](const FNakamaLeaderboardRecord& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaClientRequests.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaClientRequests.cpp deleted file mode 100644 index d6d76f2f2..000000000 --- a/Nakama/Source/NakamaBlueprints/Private/NakamaClientRequests.cpp +++ /dev/null @@ -1,4566 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaClientRequests.h" -#include "NakamaStorageObject.h" -#include "NakamaUtils.h" -#include "NakamaError.h" -#include "NakamaGroup.h" -#include "NakamaClient.h" -#include "NakamaMatch.h" -#include "NakamaFriend.h" -#include "NakamaNotification.h" -#include "NakamaRPC.h" -#include "NakamaChannelTypes.h" -#include "NakamaLeaderboard.h" -#include "NakamaTournament.h" - -UNakamaClientAuthenticateCustom* UNakamaClientAuthenticateCustom::AuthenticateCustom(UNakamaClient* Client, - FString UserID, FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateCustom* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserID = UserID; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateCustom::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateCustom(UserID, Username, bCreateAccount, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateEmail* UNakamaClientAuthenticateEmail::AuthenticateEmail(UNakamaClient* Client, FString Email, FString Password, - FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateEmail* Node = NewObject(); - Node->NakamaClient = Client; - Node->Email = Email; - Node->Password = Password; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateEmail::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateEmail(Email, Password, Username, bCreateAccount, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateDevice* UNakamaClientAuthenticateDevice::AuthenticateDevice(UNakamaClient* Client, - FString DeviceID, FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateDevice* Node = NewObject(); - Node->NakamaClient = Client; - Node->DeviceID = DeviceID; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateDevice::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptUsername = FNakamaUtils::CreateOptional(Username, FString()); - - NakamaClient->AuthenticateDevice(DeviceID, bCreateAccount, OptUsername, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateSteam* UNakamaClientAuthenticateSteam::AuthenticateSteam(UNakamaClient* Client, - FString SteamToken, FString Username, bool CreateAccount, bool ImportFriends, TMap Vars) -{ - UNakamaClientAuthenticateSteam* Node = NewObject(); - Node->NakamaClient = Client; - Node->SteamToken = SteamToken; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->bImportFriends = ImportFriends; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateSteam::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateSteam(SteamToken, Username, bCreateAccount, bImportFriends, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateGoogle* UNakamaClientAuthenticateGoogle::AuthenticateGoogle(UNakamaClient* Client, - FString AccessToken, FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateGoogle* Node = NewObject(); - Node->NakamaClient = Client; - Node->AccessToken = AccessToken; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateGoogle::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateGoogle(AccessToken, Username, bCreateAccount, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateGameCenter* UNakamaClientAuthenticateGameCenter::AuthenticateGameCenter(UNakamaClient* Client, - FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, FString Signature, FString PublicKeyUrl, - FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateGameCenter* Node = NewObject(); - Node->NakamaClient = Client; - Node->PlayerId = PlayerId; - Node->BundleId = BundleId; - Node->TimeStampSeconds = TimeStampSeconds; - Node->Salt = Salt; - Node->Signature = Signature; - Node->PublicKeyUrl = PublicKeyUrl; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateGameCenter::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateGameCenter( - PlayerId, - BundleId, - TimeStampSeconds, - Salt, - Signature, - PublicKeyUrl, - Username, - bCreateAccount, - Vars, - successCallback, - errorCallback); -} - -UNakamaClientAuthenticateFacebook* UNakamaClientAuthenticateFacebook::AuthenticateFacebook(UNakamaClient* Client, - FString AccessToken, FString Username, bool CreateAccount, bool ImportFriends, TMap Vars) -{ - UNakamaClientAuthenticateFacebook* Node = NewObject(); - Node->NakamaClient = Client; - Node->AccessToken = AccessToken; - Node->Username = Username; - Node->ImportFriends = ImportFriends; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateFacebook::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateFacebook(AccessToken, Username, bCreateAccount, ImportFriends, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateApple* UNakamaClientAuthenticateApple::AuthenticateApple(UNakamaClient* Client, FString Token, - FString Username, bool CreateAccount, TMap Vars) -{ - UNakamaClientAuthenticateApple* Node = NewObject(); - Node->NakamaClient = Client; - Node->Token = Token; - Node->Username = Username; - Node->bCreateAccount = CreateAccount; - Node->Vars = Vars; - - return Node; -} - -void UNakamaClientAuthenticateApple::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateApple(Token, Username, bCreateAccount, Vars, successCallback, errorCallback); -} - -UNakamaClientAuthenticateRefresh* UNakamaClientAuthenticateRefresh::AuthenticateRefresh(UNakamaClient* Client, UNakamaSession* Session) -{ - UNakamaClientAuthenticateRefresh* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - - return Node; -} - -void UNakamaClientAuthenticateRefresh::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateRefresh(UserSession, successCallback, errorCallback); -} - -UNakamaClientLinkCustom* UNakamaClientLinkCustom::LinkCustom(UNakamaClient* Client, UNakamaSession* Session, - FString CustomId) -{ - UNakamaClientLinkCustom* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->CustomId = CustomId; - - return Node; -} - -void UNakamaClientLinkCustom::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkCustom(UserSession, CustomId, successCallback, errorCallback); -} - -UNakamaClientUnLinkCustom* UNakamaClientUnLinkCustom::UnLinkCustom(UNakamaClient* Client, UNakamaSession* Session, - FString CustomId) -{ - UNakamaClientUnLinkCustom* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->CustomId = CustomId; - - return Node; -} - -void UNakamaClientUnLinkCustom::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkCustom(UserSession, CustomId, successCallback, errorCallback); -} - -UNakamaClientUnLinkDevice* UNakamaClientUnLinkDevice::UnLinkDevice(UNakamaClient* Client, UNakamaSession* Session, - FString DeviceId) -{ - UNakamaClientUnLinkDevice* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->DeviceId = DeviceId; - - return Node; -} - -void UNakamaClientUnLinkDevice::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkDevice(UserSession, DeviceId, successCallback, errorCallback); -} - -UNakamaClientUnLinkEmail* UNakamaClientUnLinkEmail::UnLinkEmail(UNakamaClient* Client, UNakamaSession* Session, - FString Email, FString Password) -{ - UNakamaClientUnLinkEmail* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Email = Email; - Node->Password = Password; - - return Node; -} - -void UNakamaClientUnLinkEmail::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkEmail(UserSession, Email, Password, successCallback, errorCallback); -} - -UNakamaClientUnLinkFacebook* UNakamaClientUnLinkFacebook::UnLinkFacebook(UNakamaClient* Client, UNakamaSession* Session, - FString AccessToken) -{ - UNakamaClientUnLinkFacebook* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->AccessToken = AccessToken; - - return Node; -} - -void UNakamaClientUnLinkFacebook::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkFacebook(UserSession, AccessToken, successCallback, errorCallback); -} - -UNakamaClientUnLinkGameCenter* UNakamaClientUnLinkGameCenter::UnLinkGameCenter(UNakamaClient* Client, - UNakamaSession* Session, FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, - FString Signature, FString PublicKeyUrl) -{ - UNakamaClientUnLinkGameCenter* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->PlayerId = PlayerId; - Node->BundleId = BundleId; - Node->TimeStampSeconds = TimeStampSeconds; - Node->Salt = Salt; - Node->Signature = Signature; - Node->PublicKeyUrl = PublicKeyUrl; - - return Node; -} - -void UNakamaClientUnLinkGameCenter::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkGameCenter( - UserSession, - PlayerId, - BundleId, - TimeStampSeconds, - Salt, - Signature, - PublicKeyUrl, - successCallback, - errorCallback - ); - -} - -UNakamaClientUnLinkGoogle* UNakamaClientUnLinkGoogle::UnLinkGoogle(UNakamaClient* Client, UNakamaSession* Session, - FString AccessToken) -{ - UNakamaClientUnLinkGoogle* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->AccessToken = AccessToken; - - return Node; -} - -void UNakamaClientUnLinkGoogle::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkGoogle(UserSession, AccessToken, successCallback, errorCallback); -} - -UNakamaClientUnLinkSteam* UNakamaClientUnLinkSteam::UnLinkSteam(UNakamaClient* Client, UNakamaSession* Session, - FString SteamToken) -{ - UNakamaClientUnLinkSteam* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->SteamToken = SteamToken; - - return Node; -} - -void UNakamaClientUnLinkSteam::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkSteam(UserSession, SteamToken, successCallback, errorCallback); -} - -UNakamaClientUnLinkApple* UNakamaClientUnLinkApple::UnLinkApple(UNakamaClient* Client, UNakamaSession* Session, - FString Token) -{ - UNakamaClientUnLinkApple* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Token = Token; - - return Node; -} - -void UNakamaClientUnLinkApple::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UnLinkApple(UserSession, Token, successCallback, errorCallback); -} - -UNakamaClientRefreshSession* UNakamaClientRefreshSession::RefreshSession(UNakamaClient* Client, UNakamaSession* Session) -{ - UNakamaClientRefreshSession* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - - return Node; -} - -void UNakamaClientRefreshSession::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](UNakamaSession* session) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, session); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->AuthenticateRefresh(UserSession, successCallback, errorCallback); -} - -UNakamaClientImportFacebookFriends* UNakamaClientImportFacebookFriends::ImportFacebookFriends(UNakamaClient* Client, - UNakamaSession* Session, FString Token, bool Reset) -{ - UNakamaClientImportFacebookFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Token = Token; - Node->Reset = Reset; - - return Node; -} - -void UNakamaClientImportFacebookFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->ImportFacebookFriends(UserSession, Token, Reset, successCallback, errorCallback); -} - -UNakamaClientImportSteamFriends* UNakamaClientImportSteamFriends::ImportSteamFriends(UNakamaClient* Client, - UNakamaSession* Session, FString SteamToken, bool Reset) -{ - UNakamaClientImportSteamFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->SteamToken = SteamToken; - Node->Reset = Reset; - - return Node; -} - -void UNakamaClientImportSteamFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->ImportSteamFriends(UserSession, SteamToken, Reset, successCallback, errorCallback); -} - -UNakamaClientGetUserAccount* UNakamaClientGetUserAccount::GetUserAccount(UNakamaClient* Client, UNakamaSession* Session) -{ - UNakamaClientGetUserAccount* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - - return Node; -} - -void UNakamaClientGetUserAccount::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - // Get Account: - auto successCallback = [this](const FNakamaAccount& Account) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Account); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->GetAccount(UserSession, successCallback, errorCallback); -} - -UNakamaClientGetUsers* UNakamaClientGetUsers::GetUsers(UNakamaClient* Client, UNakamaSession* Session, - TArray UserIds, TArray Usernames, TArray FacebookIds) -{ - UNakamaClientGetUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->UserIds = UserIds; - Node->Usernames = Usernames; - Node->FacebookIds = FacebookIds; - - return Node; -} - -void UNakamaClientGetUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaUserList& UserList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, UserList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->GetUsers(UserSession, UserIds, Usernames, FacebookIds, successCallback, errorCallback); -} - -UNakamaClientUpdateAccount* UNakamaClientUpdateAccount::UpdateAccount(UNakamaClient* Client, UNakamaSession* Session, - FString Username, FString DisplayName, FString AvatarUrl, FString LanguageTag, FString Location, FString Timezone) -{ - UNakamaClientUpdateAccount* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Username = Username; - Node->DisplayName = DisplayName; - Node->AvatarUrl = AvatarUrl; - Node->LanguageTag = LanguageTag; - Node->Location = Location; - Node->Timezone = Timezone; - - return Node; -} - -void UNakamaClientUpdateAccount::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->UpdateAccount( - UserSession, - Username, - DisplayName, - AvatarUrl, - LanguageTag, - Location, - Timezone, - successCallback, - errorCallback - ); -} - -UNakamaClientListMatches* UNakamaClientListMatches::ListMatches(UNakamaClient* Client, UNakamaSession* Session, - int32 MinSize, int32 MaxSize, int32 Limit, FString Label, FString Query, bool Authoritative) -{ - UNakamaClientListMatches* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->MinSize = MinSize; - Node->MaxSize = MaxSize; - Node->Limit = Limit; - Node->Label = Label; - Node->Query = Query; - Node->Authoritative = Authoritative; - - return Node; -} - -void UNakamaClientListMatches::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaMatchList& MatchList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, MatchList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLabel = FNakamaUtils::CreateOptional(Label, FString()); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptMinSize = FNakamaUtils::CreateOptional(MinSize, 0); - const auto OptMaxSize = FNakamaUtils::CreateOptional(MaxSize, 0); - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - //const auto OptAuthoritative = FNakamaUtils::CreateOptional(Authoritative, false); - - NakamaClient->ListMatches( - UserSession, - OptMinSize, - OptMaxSize, - OptLimit, - OptLabel, - OptQuery, - Authoritative, - successCallback, - errorCallback - ); -} - -UNakamaClientGetFriends* UNakamaClientGetFriends::GetFriends(UNakamaClient* Client, UNakamaSession* Session, - int32 Limit, ENakamaFriendState State, FString Cursor) -{ - UNakamaClientGetFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Limit = Limit; - Node->State = State; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientGetFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaFriendList& Friends) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Friends); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" Friend States are requested, we return an empty Enum Object - if(State == ENakamaFriendState::ALL) - { - NakamaClient->ListFriends(UserSession, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - NakamaClient->ListFriends(UserSession, OptLimit, State, Cursor, successCallback, errorCallback); - } -} - -UNakamaClientAddFriends* UNakamaClientAddFriends::AddFriends(UNakamaClient* Client, UNakamaSession* Session, - TArray Ids, TArray Usernames) -{ - UNakamaClientAddFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Ids = Ids; - Node->Usernames = Usernames; - - return Node; -} - -void UNakamaClientAddFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->AddFriends(UserSession, Ids, Usernames, successCallback, errorCallback); -} - - -UNakamaClientRemoveFriends* UNakamaClientRemoveFriends::RemoveFriends(UNakamaClient* Client, UNakamaSession* Session, - TArray Ids, TArray Usernames) -{ - UNakamaClientRemoveFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Ids = Ids; - Node->Usernames = Usernames; - - return Node; -} - -void UNakamaClientRemoveFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DeleteFriends(UserSession, Ids, Usernames, successCallback, errorCallback); -} - -UNakamaClientBlockFriends* UNakamaClientBlockFriends::BlockFriends(UNakamaClient* Client, UNakamaSession* Session, - TArray Ids, TArray Usernames) -{ - UNakamaClientBlockFriends* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Ids = Ids; - Node->Usernames = Usernames; - - return Node; -} - -void UNakamaClientBlockFriends::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->BlockFriends(UserSession, Ids, Usernames, successCallback, errorCallback); -} - -UNakamaClientCreateGroup* UNakamaClientCreateGroup::CreateGroup(UNakamaClient* Client, UNakamaSession* Session, - FString GroupName, FString Description, FString AvatarUrl, FString LanguageTag, bool Open, int32 MaxMembers) -{ - UNakamaClientCreateGroup* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupName = GroupName; - Node->Description = Description; - Node->AvatarUrl = AvatarUrl; - Node->LanguageTag = LanguageTag; - Node->Open = Open; - Node->MaxMembers = MaxMembers; - - return Node; -} - -void UNakamaClientCreateGroup::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaGroup& Group) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Group); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptMaxCount = FNakamaUtils::CreateOptional(MaxMembers, 0); - - NakamaClient->CreateGroup( - UserSession, - GroupName, - Description, - AvatarUrl, - LanguageTag, - Open, - OptMaxCount, - successCallback, - errorCallback - ); -} - -UNakamaClientListGroups* UNakamaClientListGroups::ListGroups(UNakamaClient* Client, UNakamaSession* Session, - FString GroupNameFilter, int32 Limit, FString Cursor) -{ - UNakamaClientListGroups* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupNameFilter = GroupNameFilter; - Node->Limit = Limit; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListGroups::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaGroupList& Groups) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Groups); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->ListGroups(UserSession, GroupNameFilter, Limit, Cursor, successCallback, errorCallback); -} - -UNakamaClientJoinGroup* UNakamaClientJoinGroup::JoinGroup(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId) -{ - UNakamaClientJoinGroup* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - - return Node; -} - -void UNakamaClientJoinGroup::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->JoinGroup(UserSession, GroupId, successCallback, errorCallback); -} - -UNakamaClientListUserGroups* UNakamaClientListUserGroups::ListUserGroups(UNakamaClient* Client, UNakamaSession* Session, - FString UserId, int32 Limit, ENakamaGroupState State, FString Cursor) -{ - UNakamaClientListUserGroups* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->UserId = UserId; - Node->Limit = Limit; - Node->State = State; - Node->Cursor = Cursor; - - return Node; -} - -// Note: Does not get members! -void UNakamaClientListUserGroups::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaUserGroupList& UserGroupList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, UserGroupList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" States are requested, we return an empty Enum Object - if(State == ENakamaGroupState::ALL) - { - NakamaClient->ListUserGroups(UserSession, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - NakamaClient->ListUserGroups(UserSession, OptLimit, State, Cursor, successCallback, errorCallback); - } - -} - -UNakamaClientListListGroupUsers* UNakamaClientListListGroupUsers::ListGroupUsers(UNakamaClient* Client, - UNakamaSession* Session, FString GroupId, int32 Limit, ENakamaGroupState State, FString Cursor) -{ - UNakamaClientListListGroupUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->Limit = Limit; - Node->State = State; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListListGroupUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaGroupUsersList& GroupUsersList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, GroupUsersList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" States are requested, we return an empty Enum Object - if(State == ENakamaGroupState::ALL) - { - NakamaClient->ListGroupUsers(UserSession, GroupId, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - NakamaClient->ListGroupUsers(UserSession, GroupId, OptLimit, State, Cursor, successCallback, errorCallback); - } -} - -UNakamaClientUpdateGroup* UNakamaClientUpdateGroup::UpdateGroup(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId, FString Name, FString Description, FString AvatarUrl, FString LanguageTag, bool Open) -{ - UNakamaClientUpdateGroup* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->Name = Name; - Node->Description = Description; - Node->AvatarUrl = AvatarUrl; - Node->LanguageTag = LanguageTag; - Node->Open = Open; - - return Node; -} - -void UNakamaClientUpdateGroup::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - const auto OptName = FNakamaUtils::CreateOptional(Name, FString()); - const auto OptDescription = FNakamaUtils::CreateOptional(Description, FString()); - const auto OptAvatarUrl = FNakamaUtils::CreateOptional(AvatarUrl, FString()); - const auto OptLanguageTag = FNakamaUtils::CreateOptional(LanguageTag, FString()); - - NakamaClient->UpdateGroup( - UserSession, - GroupId, - OptName, - OptDescription, - OptAvatarUrl, - OptLanguageTag, - Open, - successCallback, - errorCallback - ); -} - -UNakamaClientLeaveGroup* UNakamaClientLeaveGroup::LeaveGroup(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId) -{ - UNakamaClientLeaveGroup* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - - return Node; -} - -void UNakamaClientLeaveGroup::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LeaveGroup(UserSession, GroupId, successCallback, errorCallback); -} - -UNakamaClientAddGroupUsers* UNakamaClientAddGroupUsers::AddGroupUsers(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId, TArray UserIds) -{ - UNakamaClientAddGroupUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaClientAddGroupUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->AddGroupUsers(UserSession, GroupId, UserIds, successCallback, errorCallback); -} - -UNakamaClientPromoteGroupUsers* UNakamaClientPromoteGroupUsers::PromoteGroupUsers(UNakamaClient* Client, - UNakamaSession* Session, FString GroupId, TArray UserIds) -{ - UNakamaClientPromoteGroupUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaClientPromoteGroupUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->PromoteGroupUsers(UserSession, GroupId, UserIds, successCallback, errorCallback); -} - -UNakamaClientKickGroupUsers* UNakamaClientKickGroupUsers::KickGroupUsers(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId, TArray UserIds) -{ - UNakamaClientKickGroupUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaClientKickGroupUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->KickGroupUsers(UserSession, GroupId, UserIds, successCallback, errorCallback); -} - -UNakamaClientDemoteGroupUsers* UNakamaClientDemoteGroupUsers::DemoteGroupUsers(UNakamaClient* Client, - UNakamaSession* Session, FString GroupId, TArray UserIds) -{ - UNakamaClientDemoteGroupUsers* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaClientDemoteGroupUsers::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DemoteGroupUsers(UserSession, GroupId, UserIds, successCallback, errorCallback); -} - -UNakamaClientDeleteGroup* UNakamaClientDeleteGroup::DeleteGroup(UNakamaClient* Client, UNakamaSession* Session, - FString GroupId) -{ - UNakamaClientDeleteGroup* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->GroupId = GroupId; - - return Node; -} - -void UNakamaClientDeleteGroup::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DeleteGroup(UserSession, GroupId, successCallback, errorCallback); -} - -UNakamaClientListNotifications* UNakamaClientListNotifications::ListNotifications(UNakamaClient* Client, - UNakamaSession* Session, int32 Limit, FString Cursor) -{ - UNakamaClientListNotifications* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Limit = Limit; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListNotifications::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaNotificationList& NotificationList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, NotificationList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCacheableCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - NakamaClient->ListNotifications(UserSession, OptLimit, OptCacheableCursor, successCallback, errorCallback); -} - -UNakamaClientDeleteNotifications* UNakamaClientDeleteNotifications::DeleteNotifications(UNakamaClient* Client, - UNakamaSession* Session, TArray NotificationIds) -{ - UNakamaClientDeleteNotifications* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->NotificationIds = NotificationIds; - - return Node; -} - -void UNakamaClientDeleteNotifications::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DeleteNotifications(UserSession, NotificationIds, successCallback, errorCallback); -} - -UNakamaClientWriteStorageObjects* UNakamaClientWriteStorageObjects::WriteStorageObjects(UNakamaClient* Client, - UNakamaSession* Session, TArray StorageObjectsData) -{ - UNakamaClientWriteStorageObjects* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->StorageObjectsData = StorageObjectsData; - - return Node; -} - -void UNakamaClientWriteStorageObjects::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaStoreObjectAcks& StorageObjectAcks) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, StorageObjectAcks); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->WriteStorageObjects(UserSession, StorageObjectsData, successCallback, errorCallback); -} - -UNakamaClientReadStorageObjects* UNakamaClientReadStorageObjects::ReadStorageObjects(UNakamaClient* Client, - UNakamaSession* Session, TArray StorageObjectsData) -{ - UNakamaClientReadStorageObjects* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->StorageObjectsData = StorageObjectsData; - - return Node; -} - -void UNakamaClientReadStorageObjects::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaStorageObjectList& StorageObjectList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, StorageObjectList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->ReadStorageObjects(UserSession, StorageObjectsData, successCallback, errorCallback); -} - -UNakamaClientListtorageObjects* UNakamaClientListtorageObjects::ListStorageObjects(UNakamaClient* Client, - UNakamaSession* Session, FString Collection, FString UserId, int32 Limit, FString Cursor) -{ - UNakamaClientListtorageObjects* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Collection = Collection; - Node->UserId = UserId; - Node->Limit = Limit; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListtorageObjects::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaStorageObjectList& StorageObjectList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, StorageObjectList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - if(UserId.IsEmpty()) - { - NakamaClient->ListStorageObjects(UserSession, Collection, OptLimit, OptCursor, successCallback, errorCallback); - } - else - { - NakamaClient->ListUsersStorageObjects(UserSession, Collection, UserId, OptLimit, OptCursor, successCallback, errorCallback); - } -} - -UNakamaClientRemoveStorageObjects* UNakamaClientRemoveStorageObjects::RemoveStorageObjects(UNakamaClient* Client, - UNakamaSession* Session, TArray StorageObjectsData) -{ - UNakamaClientRemoveStorageObjects* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->StorageObjectsData = StorageObjectsData; - - return Node; -} - -void UNakamaClientRemoveStorageObjects::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DeleteStorageObjects(UserSession, StorageObjectsData, successCallback, errorCallback); -} - -UNakamaClientRPC* UNakamaClientRPC::RPC(UNakamaClient* Client, UNakamaSession* Session, FString FunctionId, - FString Payload) -{ - UNakamaClientRPC* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->FunctionId = FunctionId; - Node->Payload = Payload; - - return Node; -} - -void UNakamaClientRPC::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](FNakamaRPC&& Rpc) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, MoveTemp(Rpc)); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->RPCm(UserSession, FunctionId, Payload, successCallback, errorCallback); -} - -// RPC HttpKey - -UNakamaClientRPCHttpKey* UNakamaClientRPCHttpKey::RPCHttpKey(UNakamaClient* Client, FString HttpKey, FString FunctionId, FString Payload) -{ - UNakamaClientRPCHttpKey* Node = NewObject(); - Node->NakamaClient = Client; - Node->HttpKey = HttpKey; - Node->FunctionId = FunctionId; - Node->Payload = Payload; - - return Node; -} - -void UNakamaClientRPCHttpKey::Activate() -{ - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](FNakamaRPC&& Rpc) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, MoveTemp(Rpc)); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->RPCm(HttpKey, FunctionId, MoveTemp(Payload), successCallback, errorCallback); -} - -UNakamaClientListChannelMessages* UNakamaClientListChannelMessages::ListChannelMessages(UNakamaClient* Client, - UNakamaSession* Session, FString ChannelId, int32 Limit, FString Cursor, bool Forward) -{ - UNakamaClientListChannelMessages* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->ChannelId = ChannelId; - Node->Limit = Limit; - Node->Cursor = Cursor; - Node->Forward = Forward; - - return Node; -} - -// List Channel Messages - -void UNakamaClientListChannelMessages::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannelMessageList& ChannelMessageList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelMessageList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - NakamaClient->ListChannelMessages(UserSession, ChannelId, OptLimit, OptCursor, Forward, successCallback, errorCallback); -} - -UNakamaClientWriteLeaderboardRecord* UNakamaClientWriteLeaderboardRecord::WriteLeaderboardRecord(UNakamaClient* Client, - UNakamaSession* Session, FString LeaderboardId, int64 Score, int64 SubScore, FString Metadata) -{ - UNakamaClientWriteLeaderboardRecord* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->LeaderboardId = LeaderboardId; - Node->Score = Score; - Node->SubScore = SubScore; - Node->Metadata = Metadata; - - return Node; -} - -void UNakamaClientWriteLeaderboardRecord::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaLeaderboardRecord& LeaderboardRecord) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, LeaderboardRecord); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptSubScore = FNakamaUtils::CreateOptional(SubScore, 0); - const auto OptMetadata = FNakamaUtils::CreateOptional(Metadata, FString()); - - NakamaClient->WriteLeaderboardRecord( - UserSession, - LeaderboardId, - Score, - OptSubScore, - OptMetadata, - successCallback, - errorCallback - ); -} - -UNakamaClientListLeaderboardRecords* UNakamaClientListLeaderboardRecords::ListLeaderboardRecords(UNakamaClient* Client, - UNakamaSession* Session, FString LeaderboardId, TArray OwnerIds, int32 Limit, FString Cursor, - ENakamaLeaderboardListBy ListBy) -{ - UNakamaClientListLeaderboardRecords* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->LeaderboardId = LeaderboardId; - Node->OwnerIds = OwnerIds; - Node->Limit = Limit; - Node->Cursor = Cursor; - Node->ListBy = ListBy; - - return Node; -} - -void UNakamaClientListLeaderboardRecords::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaLeaderboardRecordList& LeaderboardRecords) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, LeaderboardRecords); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - if (ListBy == ENakamaLeaderboardListBy::BY_SCORE) - { - NakamaClient->ListLeaderboardRecords( - UserSession, - LeaderboardId, - {}, // None because of listing by score - OptLimit, - OptCursor, - successCallback, - errorCallback - ); - } - else if (ListBy == ENakamaLeaderboardListBy::BY_FRIENDS) - { - NakamaClient->ListLeaderboardRecords( - UserSession, - LeaderboardId, - OwnerIds, // OwnerIds, Can be empty - OptLimit, - OptCursor, - successCallback, - errorCallback - ); - } -} - -UNakamaClientListLeaderboardRecordsAroundOwner* UNakamaClientListLeaderboardRecordsAroundOwner:: -ListLeaderboardRecordsAroundOwner(UNakamaClient* Client, UNakamaSession* Session, FString LeaderboardId, - FString OwnerId, int32 Limit) -{ - UNakamaClientListLeaderboardRecordsAroundOwner* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->LeaderboardId = LeaderboardId; - Node->OwnerId = OwnerId; - Node->Limit = Limit; - - return Node; -} - -void UNakamaClientListLeaderboardRecordsAroundOwner::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaLeaderboardRecordList& LeaderboardRecords) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, LeaderboardRecords); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - NakamaClient->ListLeaderboardRecordsAroundOwner(UserSession, LeaderboardId, OwnerId, OptLimit, successCallback, errorCallback); -} - -UNakamaClientDeleteLeaderboardRecord* UNakamaClientDeleteLeaderboardRecord::DeleteLeaderboardRecord( - UNakamaClient* Client, UNakamaSession* Session, FString LeaderboardId) -{ - UNakamaClientDeleteLeaderboardRecord* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->LeaderboardId = LeaderboardId; - - return Node; -} - -void UNakamaClientDeleteLeaderboardRecord::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->DeleteLeaderboardRecord(UserSession, LeaderboardId, successCallback, errorCallback); -} - -UNakamaClientWriteTournamentRecord* UNakamaClientWriteTournamentRecord::WriteTournamentRecord(UNakamaClient* Client, - UNakamaSession* Session, FString TournamentId, int64 Score, int64 SubScore, FString Metadata) -{ - UNakamaClientWriteTournamentRecord* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->TournamentId = TournamentId; - Node->Score = Score; - Node->SubScore = SubScore; - Node->Metadata = Metadata; - - return Node; -} - -void UNakamaClientWriteTournamentRecord::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaLeaderboardRecord& LeaderboardRecord) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, LeaderboardRecord); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptSubScore = FNakamaUtils::CreateOptional(SubScore, 0); - const auto OptMetadata = FNakamaUtils::CreateOptional(Metadata, FString()); - - NakamaClient->WriteTournamentRecord(UserSession, TournamentId, Score, OptSubScore, OptMetadata, successCallback, errorCallback); -} - -UNakamaClientListTournamentRecords* UNakamaClientListTournamentRecords::ListTournamentRecords(UNakamaClient* Client, - UNakamaSession* Session, FString TournamentId, TArray OwnerIds, int32 Limit, FString Cursor, - ENakamaLeaderboardListBy ListBy) -{ - UNakamaClientListTournamentRecords* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->TournamentId = TournamentId; - Node->OwnerIds = OwnerIds; - Node->Limit = Limit; - Node->Cursor = Cursor; - Node->ListBy = ListBy; - - return Node; -} - -void UNakamaClientListTournamentRecords::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaTournamentRecordList& TournamentRecordList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, TournamentRecordList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - NakamaClient->ListTournamentRecords( - UserSession, - TournamentId, - OptLimit, - OptCursor, - OwnerIds, - successCallback, - errorCallback - ); -} - -UNakamaClientListTournamentRecordsAroundOwner* UNakamaClientListTournamentRecordsAroundOwner:: -ListTournamentRecordsAroundOwner(UNakamaClient* Client, UNakamaSession* Session, FString TournamentId, FString OwnerId, - int32 Limit) -{ - UNakamaClientListTournamentRecordsAroundOwner* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->TournamentId = TournamentId; - Node->OwnerId = OwnerId; - Node->Limit = Limit; - - return Node; -} - -void UNakamaClientListTournamentRecordsAroundOwner::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaTournamentRecordList& TournamentRecordList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, TournamentRecordList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - NakamaClient->ListTournamentRecordsAroundOwner( - UserSession, - TournamentId, - OwnerId, - OptLimit, - successCallback, - errorCallback - ); -} - -UNakamaClientJoinTournament* UNakamaClientJoinTournament::JoinTournament(UNakamaClient* Client, UNakamaSession* Session, - FString TournamentId) -{ - UNakamaClientJoinTournament* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->TournamentId = TournamentId; - - return Node; -} - -void UNakamaClientJoinTournament::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},TournamentId); // Deviates from normal client - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - NakamaClient->JoinTournament(UserSession, TournamentId, successCallback, errorCallback); -} - -UNakamaClientListTournaments* UNakamaClientListTournaments::ListTournaments(UNakamaClient* Client, - UNakamaSession* Session, int32 CategoryStart, int32 CategoryEnd, int32 StartTime, int32 EndTime, int32 Limit, - FString Cursor) -{ - UNakamaClientListTournaments* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->CategoryStart = CategoryStart; - Node->CategoryEnd = CategoryEnd; - Node->StartTime = StartTime; - Node->EndTime = EndTime; - Node->Limit = Limit; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListTournaments::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaTournamentList& TournamentList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},TournamentList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptCategoryStart = FNakamaUtils::CreateOptional(CategoryStart, 0); - const auto OptCategoryEnd = FNakamaUtils::CreateOptional(CategoryEnd, 0); - const auto OptStartTime = FNakamaUtils::CreateOptional(StartTime, 0); - const auto OptEndTime = FNakamaUtils::CreateOptional(EndTime, 0); - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - NakamaClient->ListTournaments( - UserSession, - OptCategoryStart, - OptCategoryEnd, - OptStartTime, - OptEndTime, - OptLimit, - OptCursor, - successCallback, errorCallback - ); -} - -UNakamaClientListParties* UNakamaClientListParties::ListParties(UNakamaClient* Client, UNakamaSession* Session, - int32 Limit, bool Open, FString Query, FString Cursor) -{ - UNakamaClientListParties* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Limit = Limit; - Node->Open = Open; - Node->Query = Query; - Node->Cursor = Cursor; - - return Node; -} - -void UNakamaClientListParties::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaPartyList& PartyList) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},PartyList); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error, {}); - SetReadyToDestroy(); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptOpen = FNakamaUtils::CreateOptional(Open, false); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - NakamaClient->ListParties( - UserSession, - OptLimit, - OptOpen, - OptQuery, - OptCursor, - successCallback, errorCallback - ); -} - -UNakamaClientLinkDevice* UNakamaClientLinkDevice::LinkDevice(UNakamaClient* Client, UNakamaSession* Session, - FString DeviceId) -{ - UNakamaClientLinkDevice* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->DeviceId = DeviceId; - - return Node; -} - -void UNakamaClientLinkDevice::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkDevice(UserSession, DeviceId, successCallback, errorCallback); -} - -UNakamaClientLinkEmail* UNakamaClientLinkEmail::LinkEmail(UNakamaClient* Client, UNakamaSession* Session, FString Email, - FString Password) -{ - UNakamaClientLinkEmail* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Email = Email; - Node->Password = Password; - - return Node; -} - -void UNakamaClientLinkEmail::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkEmail(UserSession, Email, Password, successCallback, errorCallback); -} - -UNakamaClientLinkFacebook* UNakamaClientLinkFacebook::LinkFacebook(UNakamaClient* Client, UNakamaSession* Session, - FString AccessToken, bool ImportFriends) -{ - UNakamaClientLinkFacebook* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->AccessToken = AccessToken; - Node->ImportFriends = ImportFriends; - - return Node; -} - -void UNakamaClientLinkFacebook::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkFacebook(UserSession, AccessToken, ImportFriends, successCallback, errorCallback); -} - -UNakamaClientLinkGameCenter* UNakamaClientLinkGameCenter::LinkGameCenter(UNakamaClient* Client, UNakamaSession* Session, - FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, FString Signature, FString PublicKeyUrl) -{ - UNakamaClientLinkGameCenter* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->PlayerId = PlayerId; - Node->BundleId = BundleId; - Node->TimeStampSeconds = TimeStampSeconds; - Node->Salt = Salt; - Node->Signature = Signature; - Node->PublicKeyUrl = PublicKeyUrl; - - - return Node; -} - -void UNakamaClientLinkGameCenter::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkGameCenter(UserSession, PlayerId, BundleId, TimeStampSeconds, Salt, Signature, PublicKeyUrl, successCallback, errorCallback); -} - -UNakamaClientLinkGoogle* UNakamaClientLinkGoogle::LinkGoogle(UNakamaClient* Client, UNakamaSession* Session, - FString AccessToken) -{ - UNakamaClientLinkGoogle* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->AccessToken = AccessToken; - - return Node; -} - -void UNakamaClientLinkGoogle::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkGoogle(UserSession, AccessToken, successCallback, errorCallback); -} - -UNakamaClientLinkSteam* UNakamaClientLinkSteam::LinkSteam(UNakamaClient* Client, UNakamaSession* Session, - FString SteamToken) -{ - UNakamaClientLinkSteam* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->SteamToken = SteamToken; - - return Node; -} - -void UNakamaClientLinkSteam::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkSteam(UserSession, SteamToken, successCallback, errorCallback); -} - -UNakamaClientLinkApple* UNakamaClientLinkApple::LinkApple(UNakamaClient* Client, UNakamaSession* Session, FString Token) -{ - UNakamaClientLinkApple* Node = NewObject(); - Node->NakamaClient = Client; - Node->UserSession = Session; - Node->Token = Token; - - return Node; -} - -void UNakamaClientLinkApple::Activate() -{ - // Check validity of client and session - if (!NakamaClient && !UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!NakamaClient) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FNakamaError Error = FNakamaUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(NakamaClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - NakamaClient->LinkApple(UserSession, Token, successCallback, errorCallback); -} diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaLibrary.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaLibrary.cpp deleted file mode 100644 index 2782651b7..000000000 --- a/Nakama/Source/NakamaBlueprints/Private/NakamaLibrary.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaLibrary.h" - -#include "JsonObjectConverter.h" - -FNakamaChatMessage UNakamaLibrary::ChatMessageJsonToStruct(FString JsonMessage) -{ - FNakamaChatMessage parsedStruct; - - FJsonObjectConverter::JsonObjectStringToUStruct(JsonMessage, &parsedStruct, 0, 0); - - return parsedStruct; -} - -FString UNakamaLibrary::ChatMessageStructToJson(FNakamaChatMessage MessageStruct) -{ - FString Message; - FJsonObjectConverter::UStructToJsonObjectString(MessageStruct, Message, 0, 0, 0, 0, true); - return Message; -} - diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaRealtimeClientRequests.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaRealtimeClientRequests.cpp deleted file mode 100644 index 9d952541c..000000000 --- a/Nakama/Source/NakamaBlueprints/Private/NakamaRealtimeClientRequests.cpp +++ /dev/null @@ -1,1434 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaRealtimeClientRequests.h" -#include "NakamaUtils.h" -#include "NakamaChannelTypes.h" -#include "NakamaMatch.h" -#include "NakamaMatchTypes.h" -#include "NakamaParty.h" -#include "NakamaPresence.h" -#include "NakamaStatus.h" -#include "NakamaChat.h" - -UNakamaRealtimeClientConnect* UNakamaRealtimeClientConnect::Connect(UNakamaRealtimeClient* RealtimeClient, UNakamaSession *Session, bool bInShowAsOnline) -{ - UNakamaRealtimeClientConnect* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->UserSession = Session; - Node->bShowAsOnline = bInShowAsOnline; - return Node; -} - -void UNakamaRealtimeClientConnect::Activate() -{ - // Check validity of client and session - if (!RealtimeClient && !UserSession) - { - FNakamaRtError Error; - Error.Message = FString(TEXT("Client and Session Missing")); - Error.Code = ENakamaRtErrorCode::UNKNOWN; - - NAKAMA_LOG_ERROR(Error.Message); - - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - FNakamaRtError Error; - Error.Message = FString(TEXT("Session Missing")); - Error.Code = ENakamaRtErrorCode::UNKNOWN; - - NAKAMA_LOG_ERROR(Error.Message); - - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - // Connect Callback - auto connectSuccessCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - // Connection error Callback - auto connectErrorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - // NOTE: Uses lambdas here - RealtimeClient->Connect(UserSession, bShowAsOnline,connectSuccessCallback, connectErrorCallback); -} - -UNakamaRealtimeClientSendMessage* UNakamaRealtimeClientSendMessage::SendMessage(UNakamaRealtimeClient* RealtimeClient, - FString ChannelId, FString Content) -{ - UNakamaRealtimeClientSendMessage* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->ChannelId = ChannelId; - Node->Content = Content; - - return Node; -} - -void UNakamaRealtimeClientSendMessage::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelMessageAck); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->WriteChatMessage(ChannelId, Content, successCallback, errorCallback); -} - -UNakamaRealtimeClientSendDirectMessage* UNakamaRealtimeClientSendDirectMessage::SendDirectMessage( - UNakamaRealtimeClient* RealtimeClient, FString UserID, FString Content) -{ - UNakamaRealtimeClientSendDirectMessage* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->UserID = UserID; - Node->Content = Content; - - return Node; -} - -void UNakamaRealtimeClientSendDirectMessage::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelMessageAck); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->WriteChatMessage(UserID, Content, successCallback, errorCallback); -} - -UNakamaRealtimeClientUpdateChatMessage* UNakamaRealtimeClientUpdateChatMessage::UpdateChatMessage( - UNakamaRealtimeClient* RealtimeClient, FString ChannelId, FString MessageId, FString Content) -{ - UNakamaRealtimeClientUpdateChatMessage* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->ChannelId = ChannelId; - Node->MessageId = MessageId; - Node->Content = Content; - - return Node; -} - -void UNakamaRealtimeClientUpdateChatMessage::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelMessageAck); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if(!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->UpdateChatMessage(ChannelId, MessageId, Content, successCallback, errorCallback); -} - -UNakamaRealtimeClientRemoveChatMessage* UNakamaRealtimeClientRemoveChatMessage::RemoveChatMessage(UNakamaRealtimeClient* RealtimeClient, FString ChannelId, FString MessageId) -{ - UNakamaRealtimeClientRemoveChatMessage* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->ChannelId = ChannelId; - Node->MessageId = MessageId; - - return Node; -} - -void UNakamaRealtimeClientRemoveChatMessage::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelMessageAck); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->RemoveChatMessage(ChannelId, MessageId, successCallback, errorCallback); -} - - -UNakamaRealtimeClientJoinChat* UNakamaRealtimeClientJoinChat::JoinChat(UNakamaRealtimeClient* RealtimeClient, - FString ChatId, ENakamaChannelType ChannelType, bool Persistence, bool Hidden) -{ - UNakamaRealtimeClientJoinChat* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->ChatId = ChatId; - Node->ChannelType = ChannelType; - Node->Persistence = Persistence; - Node->Hidden = Hidden; - - return Node; -} - -void UNakamaRealtimeClientJoinChat::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaChannel& Channel) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Channel); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->JoinChat(ChatId, ChannelType, Persistence, Hidden, successCallback, errorCallback); -} - -UNakamaRealtimeClientLeaveChat* UNakamaRealtimeClientLeaveChat::LeaveChat(UNakamaRealtimeClient* RealtimeClient, - FString ChannelId) -{ - UNakamaRealtimeClientLeaveChat* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->ChannelId = ChannelId; - - return Node; -} - -void UNakamaRealtimeClientLeaveChat::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, ChannelId); // Channel Parameter Deviates from the other C++ Client - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->LeaveChat(ChannelId, successCallback, errorCallback); -} - -UNakamaRealtimeClientAddMatchmaker* UNakamaRealtimeClientAddMatchmaker::AddMatchmaker( - UNakamaRealtimeClient* RealtimeClient, int32 MinCount, int32 MaxCount, FString Query, - TMap StringProperties, TMap NumericProperties, int32 CountMultiple, - bool IgnoreCountMultiple) -{ - UNakamaRealtimeClientAddMatchmaker* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->MinCount = MinCount; - Node->MaxCount = MaxCount; - Node->Query = Query; - Node->StringProperties = StringProperties; - Node->NumericProperties = NumericProperties; - Node->CountMultiple = CountMultiple; - Node->IgnoreCountMultiple = IgnoreCountMultiple; - - return Node; -} - -void UNakamaRealtimeClientAddMatchmaker::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaMatchmakerTicket& MatchmakerTicket) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, MatchmakerTicket); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - auto OptMinCount = FNakamaUtils::CreateOptional(MinCount, 0); - auto OptMaxCount = FNakamaUtils::CreateOptional(MaxCount, 0); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCountMultiple = FNakamaUtils::CreateOptional(CountMultiple, 0); - - // NOTE: Unreal 4.27 Blueprints does not support TMap with Double so we use float for Blueprints instead - const TMap NumericPropertiesDouble = FNakamaUtils::ConvertFloatMapToDouble(NumericProperties); - - RealtimeClient->AddMatchmaker( - OptMinCount, - OptMaxCount, - OptQuery, - StringProperties, - NumericPropertiesDouble, - OptCountMultiple, - successCallback, - errorCallback - ); -} - -UNakamaRealtimeClientLeaveMatchmaker* UNakamaRealtimeClientLeaveMatchmaker::LeaveMatchmaker( - UNakamaRealtimeClient* RealtimeClient, FString Ticket) -{ - UNakamaRealtimeClientLeaveMatchmaker* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->Ticket = Ticket; - - return Node; -} - -void UNakamaRealtimeClientLeaveMatchmaker::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Ticket); // Deviates from the other C++ Client by returning Ticket - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->RemoveMatchmaker(Ticket, successCallback, errorCallback); -} - -UNakamaRealtimeClientUpdateStatus* UNakamaRealtimeClientUpdateStatus::UpdateStatus( - UNakamaRealtimeClient* RealtimeClient, FString StatusMessage) -{ - UNakamaRealtimeClientUpdateStatus* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->StatusMessage = StatusMessage; - - return Node; -} - -void UNakamaRealtimeClientUpdateStatus::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->UpdateStatus(StatusMessage, successCallback, errorCallback); -} - -UNakamaRealtimeClientSetAppearOffline* UNakamaRealtimeClientSetAppearOffline::SetAppearOffline( - UNakamaRealtimeClient* RealtimeClient) -{ - UNakamaRealtimeClientSetAppearOffline* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - - return Node; -} - -void UNakamaRealtimeClientSetAppearOffline::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->UpdateStatus("", successCallback, errorCallback); // "Invisible" Status -} - -UNakamaRealtimeClientFollowUsers* UNakamaRealtimeClientFollowUsers::FollowUsers(UNakamaRealtimeClient* RealtimeClient, - TArray UserIds) -{ - UNakamaRealtimeClientFollowUsers* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaRealtimeClientFollowUsers::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaStatus& Status) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Status); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->FollowUsers(UserIds, successCallback, errorCallback); -} - -UNakamaRealtimeClientUnFollowUsers* UNakamaRealtimeClientUnFollowUsers::UnFollowUsers( - UNakamaRealtimeClient* RealtimeClient, TArray UserIds) -{ - UNakamaRealtimeClientUnFollowUsers* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->UserIds = UserIds; - - return Node; -} - -void UNakamaRealtimeClientUnFollowUsers::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->UnfollowUsers(UserIds, successCallback, errorCallback); -} - -UNakamaRealtimeClientCreateMatch* UNakamaRealtimeClientCreateMatch::CreateMatch(UNakamaRealtimeClient* RealtimeClient) -{ - UNakamaRealtimeClientCreateMatch* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - - return Node; -} - -void UNakamaRealtimeClientCreateMatch::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaMatch& Match) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},Match); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->CreateMatch(successCallback, errorCallback); -} - -UNakamaRealtimeClientJoinMatch* UNakamaRealtimeClientJoinMatch::JoinMatch(UNakamaRealtimeClient* RealtimeClient, - FString MatchId, TMap MetaData) -{ - UNakamaRealtimeClientJoinMatch* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->MatchId = MatchId; - Node->MetaData = MetaData; - - return Node; -} - -void UNakamaRealtimeClientJoinMatch::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaMatch& Match) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},Match); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->JoinMatch(MatchId, MetaData, successCallback, errorCallback); -} - -UNakamaRealtimeClientJoinMatchByToken* UNakamaRealtimeClientJoinMatchByToken::JoinMatchByToken( - UNakamaRealtimeClient* RealtimeClient, FString Token) -{ - UNakamaRealtimeClientJoinMatchByToken* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->Token = Token; - - return Node; -} - -void UNakamaRealtimeClientJoinMatchByToken::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaMatch& Match) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({},Match); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->JoinMatchByToken(Token, successCallback, errorCallback); -} - - - -UNakamaRealtimeClientLeaveMatch* UNakamaRealtimeClientLeaveMatch::LeaveMatch(UNakamaRealtimeClient* RealtimeClient, - FString MatchId) -{ - UNakamaRealtimeClientLeaveMatch* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->MatchId = MatchId; - - return Node; -} - -void UNakamaRealtimeClientLeaveMatch::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, MatchId); // Deviation from C++ SDK by returning the MatchId - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->LeaveMatch(MatchId, successCallback, errorCallback); -} - -UNakamaRealtimeClientCreateParty* UNakamaRealtimeClientCreateParty::CreateParty(UNakamaRealtimeClient* RealtimeClient, bool Open, - int32 MaxSize) -{ - UNakamaRealtimeClientCreateParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->Open = Open; - Node->MaxSize = MaxSize; - - return Node; -} - -void UNakamaRealtimeClientCreateParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaParty& Party) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Party); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->CreateParty(Open, MaxSize, successCallback, errorCallback); -} - -UNakamaRealtimeClientJoinParty* UNakamaRealtimeClientJoinParty::JoinParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId) -{ - UNakamaRealtimeClientJoinParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - - return Node; -} - -void UNakamaRealtimeClientJoinParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, PartyId); // Deviates from C++ SDK by returning the PartyId - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->JoinParty(PartyId, successCallback, errorCallback); -} - -UNakamaRealtimeClientLeaveParty* UNakamaRealtimeClientLeaveParty::LeaveParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId) -{ - UNakamaRealtimeClientLeaveParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - - return Node; -} - -void UNakamaRealtimeClientLeaveParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, PartyId); // Deviates from C++ SDK by returning the PartyId - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->LeaveParty(PartyId, successCallback, errorCallback); -} - -UNakamaRealtimeClientListPartyJoinRequests* UNakamaRealtimeClientListPartyJoinRequests::ListPartyJoinRequests( - UNakamaRealtimeClient* RealtimeClient, FString PartyId) -{ - UNakamaRealtimeClientListPartyJoinRequests* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - - return Node; -} - -void UNakamaRealtimeClientListPartyJoinRequests::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaPartyJoinRequest& PartyJoinRequest) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, PartyJoinRequest); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->ListPartyJoinRequests(PartyId, successCallback, errorCallback); -} - -UNakamaRealtimeClientPromotePartyMember* UNakamaRealtimeClientPromotePartyMember::PromotePartyMember( - UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence PartyMember) -{ - UNakamaRealtimeClientPromotePartyMember* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - Node->PartyMember = PartyMember; - - return Node; -} - -void UNakamaRealtimeClientPromotePartyMember::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->PromotePartyMember(PartyId, PartyMember, successCallback, errorCallback); -} - -UNakamaRealtimeClientRemoveMatchmakerParty* UNakamaRealtimeClientRemoveMatchmakerParty::RemoveMatchmakerParty( - UNakamaRealtimeClient* RealtimeClient, FString PartyId, FString Ticket) -{ - UNakamaRealtimeClientRemoveMatchmakerParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - Node->Ticket = Ticket; - - return Node; -} - -void UNakamaRealtimeClientRemoveMatchmakerParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, Ticket); // Deviates from C++ SDK by returning the Ticket - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->RemoveMatchmakerParty(PartyId, Ticket, successCallback, errorCallback); -} - -UNakamaRealtimeClientRemovePartyMember* UNakamaRealtimeClientRemovePartyMember::RemovePartyMember( - UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence Presence) -{ - UNakamaRealtimeClientRemovePartyMember* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - Node->Presence = Presence; - - return Node; -} - -void UNakamaRealtimeClientRemovePartyMember::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->RemovePartyMember(PartyId, Presence, successCallback, errorCallback); -} - - -UNakamaRealtimeClientAcceptPartyMember* UNakamaRealtimeClientAcceptPartyMember::AcceptPartyMember( - UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence Presence) -{ - UNakamaRealtimeClientAcceptPartyMember* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - Node->Presence = Presence; - - return Node; -} - -void UNakamaRealtimeClientAcceptPartyMember::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->AcceptPartyMember(PartyId, Presence, successCallback, errorCallback); -} - -UNakamaRealtimeClientAddMatchmakerParty* UNakamaRealtimeClientAddMatchmakerParty::AddMatchmakerParty( - UNakamaRealtimeClient* RealtimeClient, FString PartyId, int32 MinCount, int32 MaxCount, FString Query, - TMap StringProperties, TMap NumericProperties, int32 CountMultiple, - bool IgnoreCountMultiple) -{ - UNakamaRealtimeClientAddMatchmakerParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - Node->MinCount = MinCount; - Node->MaxCount = MaxCount; - Node->Query = Query; - Node->StringProperties = StringProperties; - Node->NumericProperties = NumericProperties; - Node->CountMultiple = CountMultiple; - Node->IgnoreCountMultiple = IgnoreCountMultiple; - - return Node; -} - -void UNakamaRealtimeClientAddMatchmakerParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaPartyMatchmakerTicket& PartyMatchmakerTicket) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, PartyMatchmakerTicket); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - auto OptMinCount = FNakamaUtils::CreateOptional(MinCount, 0); - auto OptMaxCount = FNakamaUtils::CreateOptional(MaxCount, 0); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCountMultiple = FNakamaUtils::CreateOptional(CountMultiple, 0); - - // NOTE: Unreal 4.27 Blueprints does not support TMap with Double so we use float for Blueprints instead - const TMap NumericPropertiesDouble = FNakamaUtils::ConvertFloatMapToDouble(NumericProperties); - - RealtimeClient->AddMatchmakerParty( - PartyId, - OptMinCount, - OptMaxCount, - OptQuery, - StringProperties, - NumericPropertiesDouble, - OptCountMultiple, - successCallback, - errorCallback - ); -} - -UNakamaRealtimeClientCloseParty* UNakamaRealtimeClientCloseParty::CloseParty(UNakamaRealtimeClient* RealtimeClient, - FString PartyId) -{ - UNakamaRealtimeClientCloseParty* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->PartyId = PartyId; - - return Node; -} - -void UNakamaRealtimeClientCloseParty::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error); - SetReadyToDestroy(); - }; - - RealtimeClient->CloseParty(PartyId, successCallback, errorCallback); -} - -UNakamaRealtimeClientRPC* UNakamaRealtimeClientRPC::RPC(UNakamaRealtimeClient* RealtimeClient, const FString& FunctionId, const FString& Payload) -{ - UNakamaRealtimeClientRPC* Node = NewObject(); - Node->RealtimeClient = RealtimeClient; - Node->FunctionId = FunctionId; - Node->Payload = Payload; - - return Node; -} - -void UNakamaRealtimeClientRPC::Activate() -{ - if (!RealtimeClient) - { - const FNakamaRtError Error = FNakamaUtils::HandleInvalidRealtimeClient(); - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FNakamaRPC& rpc) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}, rpc); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - if (!FNakamaUtils::IsRealtimeClientActive(RealtimeClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(Error, {}); - SetReadyToDestroy(); - }; - - RealtimeClient->RPC(FunctionId, Payload, successCallback, errorCallback); -} diff --git a/Nakama/Source/NakamaBlueprints/Private/NakamaRtClientBlueprintLibrary.cpp b/Nakama/Source/NakamaBlueprints/Private/NakamaRtClientBlueprintLibrary.cpp new file mode 100644 index 000000000..bbfb41aa3 --- /dev/null +++ b/Nakama/Source/NakamaBlueprints/Private/NakamaRtClientBlueprintLibrary.cpp @@ -0,0 +1,3903 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ + +// Channel +UNakamaRealtimeClientChannel* UNakamaRealtimeClientChannel::Channel( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Id, + const TArray& Presences, + FNakamaRtUserPresence Self_, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo) +{ + UNakamaRealtimeClientChannel* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredId = Id; + Action->StoredPresences = Presences; + Action->StoredSelf = Self_; + Action->StoredRoomName = RoomName; + Action->StoredGroupId = GroupId; + Action->StoredUserIdOne = UserIdOne; + Action->StoredUserIdTwo = UserIdTwo; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannel::Activate() +{ + static const TCHAR* TraceScope_Channel = TEXT("NakamaRTBP_Channel"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Channel); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredId.IsEmpty()) + { + Json->SetStringField(TEXT("id"), StoredId); + } + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), StoredSelf.ToJson()); + if (!StoredRoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), StoredRoomName); + } + if (!StoredGroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), StoredGroupId); + } + if (!StoredUserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), StoredUserIdOne); + } + if (!StoredUserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), StoredUserIdTwo); + } + + StoredWebSocketSubsystem->Send(TEXT("channel"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelJoin +UNakamaRealtimeClientChannelJoin* UNakamaRealtimeClientChannelJoin::ChannelJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Target, + int32 Type, + bool Persistence, + bool Hidden) +{ + UNakamaRealtimeClientChannelJoin* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredTarget = Target; + Action->StoredType = Type; + Action->StoredPersistence = Persistence; + Action->StoredHidden = Hidden; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelJoin::Activate() +{ + static const TCHAR* TraceScope_ChannelJoin = TEXT("NakamaRTBP_ChannelJoin"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelJoin); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredTarget.IsEmpty()) + { + Json->SetStringField(TEXT("target"), StoredTarget); + } + Json->SetNumberField(TEXT("type"), StoredType); + Json->SetBoolField(TEXT("persistence"), StoredPersistence); + Json->SetBoolField(TEXT("hidden"), StoredHidden); + + StoredWebSocketSubsystem->Send(TEXT("channel_join"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelLeave +UNakamaRealtimeClientChannelLeave* UNakamaRealtimeClientChannelLeave::ChannelLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId) +{ + UNakamaRealtimeClientChannelLeave* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelLeave::Activate() +{ + static const TCHAR* TraceScope_ChannelLeave = TEXT("NakamaRTBP_ChannelLeave"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelLeave); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_leave"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelMessage +UNakamaRealtimeClientChannelMessage* UNakamaRealtimeClientChannelMessage::ChannelMessage( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + int32 Code, + FString SenderId, + FString Username, + FString Content, + FString CreateTime, + FString UpdateTime, + bool Persistent, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo) +{ + UNakamaRealtimeClientChannelMessage* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredMessageId = MessageId; + Action->StoredCode = Code; + Action->StoredSenderId = SenderId; + Action->StoredUsername = Username; + Action->StoredContent = Content; + Action->StoredCreateTime = CreateTime; + Action->StoredUpdateTime = UpdateTime; + Action->StoredPersistent = Persistent; + Action->StoredRoomName = RoomName; + Action->StoredGroupId = GroupId; + Action->StoredUserIdOne = UserIdOne; + Action->StoredUserIdTwo = UserIdTwo; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelMessage::Activate() +{ + static const TCHAR* TraceScope_ChannelMessage = TEXT("NakamaRTBP_ChannelMessage"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelMessage); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (!StoredMessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), StoredMessageId); + } + if (StoredCode != 0) + { + Json->SetNumberField(TEXT("code"), StoredCode); + } + if (!StoredSenderId.IsEmpty()) + { + Json->SetStringField(TEXT("sender_id"), StoredSenderId); + } + if (!StoredUsername.IsEmpty()) + { + Json->SetStringField(TEXT("username"), StoredUsername); + } + if (!StoredContent.IsEmpty()) + { + Json->SetStringField(TEXT("content"), StoredContent); + } + if (!StoredCreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), StoredCreateTime); + } + if (!StoredUpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), StoredUpdateTime); + } + Json->SetBoolField(TEXT("persistent"), StoredPersistent); + if (!StoredRoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), StoredRoomName); + } + if (!StoredGroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), StoredGroupId); + } + if (!StoredUserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), StoredUserIdOne); + } + if (!StoredUserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), StoredUserIdTwo); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_message"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelMessageAck +UNakamaRealtimeClientChannelMessageAck* UNakamaRealtimeClientChannelMessageAck::ChannelMessageAck( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + int32 Code, + FString Username, + FString CreateTime, + FString UpdateTime, + bool Persistent, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo) +{ + UNakamaRealtimeClientChannelMessageAck* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredMessageId = MessageId; + Action->StoredCode = Code; + Action->StoredUsername = Username; + Action->StoredCreateTime = CreateTime; + Action->StoredUpdateTime = UpdateTime; + Action->StoredPersistent = Persistent; + Action->StoredRoomName = RoomName; + Action->StoredGroupId = GroupId; + Action->StoredUserIdOne = UserIdOne; + Action->StoredUserIdTwo = UserIdTwo; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelMessageAck::Activate() +{ + static const TCHAR* TraceScope_ChannelMessageAck = TEXT("NakamaRTBP_ChannelMessageAck"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelMessageAck); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (!StoredMessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), StoredMessageId); + } + if (StoredCode != 0) + { + Json->SetNumberField(TEXT("code"), StoredCode); + } + if (!StoredUsername.IsEmpty()) + { + Json->SetStringField(TEXT("username"), StoredUsername); + } + if (!StoredCreateTime.IsEmpty()) + { + Json->SetStringField(TEXT("create_time"), StoredCreateTime); + } + if (!StoredUpdateTime.IsEmpty()) + { + Json->SetStringField(TEXT("update_time"), StoredUpdateTime); + } + Json->SetBoolField(TEXT("persistent"), StoredPersistent); + if (!StoredRoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), StoredRoomName); + } + if (!StoredGroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), StoredGroupId); + } + if (!StoredUserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), StoredUserIdOne); + } + if (!StoredUserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), StoredUserIdTwo); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_message_ack"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelMessageSend +UNakamaRealtimeClientChannelMessageSend* UNakamaRealtimeClientChannelMessageSend::ChannelMessageSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString Content) +{ + UNakamaRealtimeClientChannelMessageSend* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredContent = Content; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelMessageSend::Activate() +{ + static const TCHAR* TraceScope_ChannelMessageSend = TEXT("NakamaRTBP_ChannelMessageSend"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelMessageSend); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (!StoredContent.IsEmpty()) + { + Json->SetStringField(TEXT("content"), StoredContent); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_message_send"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelMessageUpdate +UNakamaRealtimeClientChannelMessageUpdate* UNakamaRealtimeClientChannelMessageUpdate::ChannelMessageUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + FString Content) +{ + UNakamaRealtimeClientChannelMessageUpdate* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredMessageId = MessageId; + Action->StoredContent = Content; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelMessageUpdate::Activate() +{ + static const TCHAR* TraceScope_ChannelMessageUpdate = TEXT("NakamaRTBP_ChannelMessageUpdate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelMessageUpdate); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (!StoredMessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), StoredMessageId); + } + if (!StoredContent.IsEmpty()) + { + Json->SetStringField(TEXT("content"), StoredContent); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_message_update"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelMessageRemove +UNakamaRealtimeClientChannelMessageRemove* UNakamaRealtimeClientChannelMessageRemove::ChannelMessageRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId) +{ + UNakamaRealtimeClientChannelMessageRemove* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredMessageId = MessageId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelMessageRemove::Activate() +{ + static const TCHAR* TraceScope_ChannelMessageRemove = TEXT("NakamaRTBP_ChannelMessageRemove"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelMessageRemove); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (!StoredMessageId.IsEmpty()) + { + Json->SetStringField(TEXT("message_id"), StoredMessageId); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_message_remove"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// ChannelPresenceEvent +UNakamaRealtimeClientChannelPresenceEvent* UNakamaRealtimeClientChannelPresenceEvent::ChannelPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + const TArray& Joins, + const TArray& Leaves, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo) +{ + UNakamaRealtimeClientChannelPresenceEvent* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredChannelId = ChannelId; + Action->StoredJoins = Joins; + Action->StoredLeaves = Leaves; + Action->StoredRoomName = RoomName; + Action->StoredGroupId = GroupId; + Action->StoredUserIdOne = UserIdOne; + Action->StoredUserIdTwo = UserIdTwo; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientChannelPresenceEvent::Activate() +{ + static const TCHAR* TraceScope_ChannelPresenceEvent = TEXT("NakamaRTBP_ChannelPresenceEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ChannelPresenceEvent); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredChannelId.IsEmpty()) + { + Json->SetStringField(TEXT("channel_id"), StoredChannelId); + } + if (StoredJoins.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredJoins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (StoredLeaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredLeaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + if (!StoredRoomName.IsEmpty()) + { + Json->SetStringField(TEXT("room_name"), StoredRoomName); + } + if (!StoredGroupId.IsEmpty()) + { + Json->SetStringField(TEXT("group_id"), StoredGroupId); + } + if (!StoredUserIdOne.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_one"), StoredUserIdOne); + } + if (!StoredUserIdTwo.IsEmpty()) + { + Json->SetStringField(TEXT("user_id_two"), StoredUserIdTwo); + } + + StoredWebSocketSubsystem->Send(TEXT("channel_presence_event"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Error +UNakamaRealtimeClientError* UNakamaRealtimeClientError::Error( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + int32 Code, + FString Message, + const TMap& Context) +{ + UNakamaRealtimeClientError* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredCode = Code; + Action->StoredMessage = Message; + Action->StoredContext = Context; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientError::Activate() +{ + static const TCHAR* TraceScope_Error = TEXT("NakamaRTBP_Error"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Error); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("code"), StoredCode); + if (!StoredMessage.IsEmpty()) + { + Json->SetStringField(TEXT("message"), StoredMessage); + } + if (StoredContext.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredContext) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("context"), MapObj); + } + + StoredWebSocketSubsystem->Send(TEXT("error"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Match +UNakamaRealtimeClientMatch* UNakamaRealtimeClientMatch::Match( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + bool Authoritative, + FString Label, + int32 Size, + const TArray& Presences, + FNakamaRtUserPresence Self_) +{ + UNakamaRealtimeClientMatch* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMatchId = MatchId; + Action->StoredAuthoritative = Authoritative; + Action->StoredLabel = Label; + Action->StoredSize = Size; + Action->StoredPresences = Presences; + Action->StoredSelf = Self_; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatch::Activate() +{ + static const TCHAR* TraceScope_Match = TEXT("NakamaRTBP_Match"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Match); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredMatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), StoredMatchId); + } + Json->SetBoolField(TEXT("authoritative"), StoredAuthoritative); + if (!StoredLabel.IsEmpty()) + { + Json->SetStringField(TEXT("label"), StoredLabel); + } + Json->SetNumberField(TEXT("size"), StoredSize); + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetObjectField(TEXT("self"), StoredSelf.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("match"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchCreate +UNakamaRealtimeClientMatchCreate* UNakamaRealtimeClientMatchCreate::MatchCreate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Name) +{ + UNakamaRealtimeClientMatchCreate* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredName = Name; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchCreate::Activate() +{ + static const TCHAR* TraceScope_MatchCreate = TEXT("NakamaRTBP_MatchCreate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchCreate); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredName.IsEmpty()) + { + Json->SetStringField(TEXT("name"), StoredName); + } + + StoredWebSocketSubsystem->Send(TEXT("match_create"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchData +UNakamaRealtimeClientMatchData* UNakamaRealtimeClientMatchData::MatchData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + FNakamaRtUserPresence Presence, + int64 OpCode, + const TArray& Data, + bool Reliable) +{ + UNakamaRealtimeClientMatchData* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMatchId = MatchId; + Action->StoredPresence = Presence; + Action->StoredOpCode = OpCode; + Action->StoredData = Data; + Action->StoredReliable = Reliable; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchData::Activate() +{ + static const TCHAR* TraceScope_MatchData = TEXT("NakamaRTBP_MatchData"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchData); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredMatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), StoredMatchId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + Json->SetNumberField(TEXT("op_code"), StoredOpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(StoredData)); + Json->SetBoolField(TEXT("reliable"), StoredReliable); + + StoredWebSocketSubsystem->Send(TEXT("match_data"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchDataSend +UNakamaRealtimeClientMatchDataSend* UNakamaRealtimeClientMatchDataSend::MatchDataSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + int64 OpCode, + const TArray& Data, + const TArray& Presences, + bool Reliable) +{ + UNakamaRealtimeClientMatchDataSend* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMatchId = MatchId; + Action->StoredOpCode = OpCode; + Action->StoredData = Data; + Action->StoredPresences = Presences; + Action->StoredReliable = Reliable; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchDataSend::Activate() +{ + static const TCHAR* TraceScope_MatchDataSend = TEXT("NakamaRTBP_MatchDataSend"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchDataSend); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredMatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), StoredMatchId); + } + Json->SetNumberField(TEXT("op_code"), StoredOpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(StoredData)); + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + Json->SetBoolField(TEXT("reliable"), StoredReliable); + + StoredWebSocketSubsystem->Send(TEXT("match_data_send"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchJoin +UNakamaRealtimeClientMatchJoin* UNakamaRealtimeClientMatchJoin::MatchJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TMap& Metadata) +{ + UNakamaRealtimeClientMatchJoin* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMetadata = Metadata; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchJoin::Activate() +{ + static const TCHAR* TraceScope_MatchJoin = TEXT("NakamaRTBP_MatchJoin"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchJoin); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredMetadata.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredMetadata) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("metadata"), MapObj); + } + + StoredWebSocketSubsystem->Send(TEXT("match_join"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchLeave +UNakamaRealtimeClientMatchLeave* UNakamaRealtimeClientMatchLeave::MatchLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId) +{ + UNakamaRealtimeClientMatchLeave* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMatchId = MatchId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchLeave::Activate() +{ + static const TCHAR* TraceScope_MatchLeave = TEXT("NakamaRTBP_MatchLeave"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchLeave); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredMatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), StoredMatchId); + } + + StoredWebSocketSubsystem->Send(TEXT("match_leave"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchPresenceEvent +UNakamaRealtimeClientMatchPresenceEvent* UNakamaRealtimeClientMatchPresenceEvent::MatchPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + const TArray& Joins, + const TArray& Leaves) +{ + UNakamaRealtimeClientMatchPresenceEvent* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMatchId = MatchId; + Action->StoredJoins = Joins; + Action->StoredLeaves = Leaves; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchPresenceEvent::Activate() +{ + static const TCHAR* TraceScope_MatchPresenceEvent = TEXT("NakamaRTBP_MatchPresenceEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchPresenceEvent); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredMatchId.IsEmpty()) + { + Json->SetStringField(TEXT("match_id"), StoredMatchId); + } + if (StoredJoins.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredJoins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (StoredLeaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredLeaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("match_presence_event"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchmakerAdd +UNakamaRealtimeClientMatchmakerAdd* UNakamaRealtimeClientMatchmakerAdd::MatchmakerAdd( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + int32 MinCount, + int32 MaxCount, + FString Query, + int32 CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties) +{ + UNakamaRealtimeClientMatchmakerAdd* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredMinCount = MinCount; + Action->StoredMaxCount = MaxCount; + Action->StoredQuery = Query; + Action->StoredCountMultiple = CountMultiple; + Action->StoredStringProperties = StringProperties; + Action->StoredNumericProperties = NumericProperties; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchmakerAdd::Activate() +{ + static const TCHAR* TraceScope_MatchmakerAdd = TEXT("NakamaRTBP_MatchmakerAdd"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchmakerAdd); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("min_count"), StoredMinCount); + Json->SetNumberField(TEXT("max_count"), StoredMaxCount); + if (!StoredQuery.IsEmpty()) + { + Json->SetStringField(TEXT("query"), StoredQuery); + } + if (StoredCountMultiple != 0) + { + Json->SetNumberField(TEXT("count_multiple"), StoredCountMultiple); + } + if (StoredStringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredStringProperties) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (StoredNumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredNumericProperties) + { + MapObj->SetNumberField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + + StoredWebSocketSubsystem->Send(TEXT("matchmaker_add"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchmakerMatched +UNakamaRealtimeClientMatchmakerMatched* UNakamaRealtimeClientMatchmakerMatched::MatchmakerMatched( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket, + const TArray& Users, + FNakamaRtMatchmakerMatched_MatchmakerUser Self_) +{ + UNakamaRealtimeClientMatchmakerMatched* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredTicket = Ticket; + Action->StoredUsers = Users; + Action->StoredSelf = Self_; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchmakerMatched::Activate() +{ + static const TCHAR* TraceScope_MatchmakerMatched = TEXT("NakamaRTBP_MatchmakerMatched"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchmakerMatched); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredTicket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), StoredTicket); + } + if (StoredUsers.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredUsers) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("users"), Array); + } + Json->SetObjectField(TEXT("self"), StoredSelf.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("matchmaker_matched"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchmakerRemove +UNakamaRealtimeClientMatchmakerRemove* UNakamaRealtimeClientMatchmakerRemove::MatchmakerRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket) +{ + UNakamaRealtimeClientMatchmakerRemove* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredTicket = Ticket; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchmakerRemove::Activate() +{ + static const TCHAR* TraceScope_MatchmakerRemove = TEXT("NakamaRTBP_MatchmakerRemove"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchmakerRemove); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredTicket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), StoredTicket); + } + + StoredWebSocketSubsystem->Send(TEXT("matchmaker_remove"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// MatchmakerTicket +UNakamaRealtimeClientMatchmakerTicket* UNakamaRealtimeClientMatchmakerTicket::MatchmakerTicket( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket) +{ + UNakamaRealtimeClientMatchmakerTicket* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredTicket = Ticket; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientMatchmakerTicket::Activate() +{ + static const TCHAR* TraceScope_MatchmakerTicket = TEXT("NakamaRTBP_MatchmakerTicket"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_MatchmakerTicket); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredTicket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), StoredTicket); + } + + StoredWebSocketSubsystem->Send(TEXT("matchmaker_ticket"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Notifications +UNakamaRealtimeClientNotifications* UNakamaRealtimeClientNotifications::Notifications( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Notifications) +{ + UNakamaRealtimeClientNotifications* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredNotifications = Notifications; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientNotifications::Activate() +{ + static const TCHAR* TraceScope_Notifications = TEXT("NakamaRTBP_Notifications"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Notifications); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredNotifications.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredNotifications) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("notifications"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("notifications"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Rpc +UNakamaRealtimeClientRpc* UNakamaRealtimeClientRpc::Rpc( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Id, + FString Payload, + FString HttpKey) +{ + UNakamaRealtimeClientRpc* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredId = Id; + Action->StoredPayload = Payload; + Action->StoredHttpKey = HttpKey; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientRpc::Activate() +{ + static const TCHAR* TraceScope_Rpc = TEXT("NakamaRTBP_Rpc"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Rpc); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredId.IsEmpty()) + { + Json->SetStringField(TEXT("id"), StoredId); + } + if (!StoredPayload.IsEmpty()) + { + Json->SetStringField(TEXT("payload"), StoredPayload); + } + if (!StoredHttpKey.IsEmpty()) + { + Json->SetStringField(TEXT("http_key"), StoredHttpKey); + } + + StoredWebSocketSubsystem->Send(TEXT("rpc"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Status +UNakamaRealtimeClientStatus* UNakamaRealtimeClientStatus::Status( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Presences) +{ + UNakamaRealtimeClientStatus* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPresences = Presences; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStatus::Activate() +{ + static const TCHAR* TraceScope_Status = TEXT("NakamaRTBP_Status"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Status); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("status"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StatusFollow +UNakamaRealtimeClientStatusFollow* UNakamaRealtimeClientStatusFollow::StatusFollow( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& UserIds, + const TArray& Usernames) +{ + UNakamaRealtimeClientStatusFollow* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredUserIds = UserIds; + Action->StoredUsernames = Usernames; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStatusFollow::Activate() +{ + static const TCHAR* TraceScope_StatusFollow = TEXT("NakamaRTBP_StatusFollow"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StatusFollow); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredUserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredUserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + if (StoredUsernames.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredUsernames) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("usernames"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("status_follow"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StatusPresenceEvent +UNakamaRealtimeClientStatusPresenceEvent* UNakamaRealtimeClientStatusPresenceEvent::StatusPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Joins, + const TArray& Leaves) +{ + UNakamaRealtimeClientStatusPresenceEvent* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredJoins = Joins; + Action->StoredLeaves = Leaves; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStatusPresenceEvent::Activate() +{ + static const TCHAR* TraceScope_StatusPresenceEvent = TEXT("NakamaRTBP_StatusPresenceEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StatusPresenceEvent); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredJoins.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredJoins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (StoredLeaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredLeaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("status_presence_event"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StatusUnfollow +UNakamaRealtimeClientStatusUnfollow* UNakamaRealtimeClientStatusUnfollow::StatusUnfollow( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& UserIds) +{ + UNakamaRealtimeClientStatusUnfollow* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredUserIds = UserIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStatusUnfollow::Activate() +{ + static const TCHAR* TraceScope_StatusUnfollow = TEXT("NakamaRTBP_StatusUnfollow"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StatusUnfollow); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (StoredUserIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredUserIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("user_ids"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("status_unfollow"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StatusUpdate +UNakamaRealtimeClientStatusUpdate* UNakamaRealtimeClientStatusUpdate::StatusUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Status) +{ + UNakamaRealtimeClientStatusUpdate* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredStatus = Status; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStatusUpdate::Activate() +{ + static const TCHAR* TraceScope_StatusUpdate = TEXT("NakamaRTBP_StatusUpdate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StatusUpdate); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredStatus.IsEmpty()) + { + Json->SetStringField(TEXT("status"), StoredStatus); + } + + StoredWebSocketSubsystem->Send(TEXT("status_update"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StreamData +UNakamaRealtimeClientStreamData* UNakamaRealtimeClientStreamData::StreamData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FNakamaRtStream Stream, + FNakamaRtUserPresence Sender, + FString Data, + bool Reliable) +{ + UNakamaRealtimeClientStreamData* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredStream = Stream; + Action->StoredSender = Sender; + Action->StoredData = Data; + Action->StoredReliable = Reliable; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStreamData::Activate() +{ + static const TCHAR* TraceScope_StreamData = TEXT("NakamaRTBP_StreamData"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StreamData); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), StoredStream.ToJson()); + Json->SetObjectField(TEXT("sender"), StoredSender.ToJson()); + if (!StoredData.IsEmpty()) + { + Json->SetStringField(TEXT("data"), StoredData); + } + Json->SetBoolField(TEXT("reliable"), StoredReliable); + + StoredWebSocketSubsystem->Send(TEXT("stream_data"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// StreamPresenceEvent +UNakamaRealtimeClientStreamPresenceEvent* UNakamaRealtimeClientStreamPresenceEvent::StreamPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FNakamaRtStream Stream, + const TArray& Joins, + const TArray& Leaves) +{ + UNakamaRealtimeClientStreamPresenceEvent* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredStream = Stream; + Action->StoredJoins = Joins; + Action->StoredLeaves = Leaves; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientStreamPresenceEvent::Activate() +{ + static const TCHAR* TraceScope_StreamPresenceEvent = TEXT("NakamaRTBP_StreamPresenceEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_StreamPresenceEvent); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + Json->SetObjectField(TEXT("stream"), StoredStream.ToJson()); + if (StoredJoins.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredJoins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (StoredLeaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredLeaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("stream_presence_event"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Ping +UNakamaRealtimeClientPing* UNakamaRealtimeClientPing::Ping( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem) +{ + UNakamaRealtimeClientPing* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPing::Activate() +{ + static const TCHAR* TraceScope_Ping = TEXT("NakamaRTBP_Ping"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Ping); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + + StoredWebSocketSubsystem->Send(TEXT("ping"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Pong +UNakamaRealtimeClientPong* UNakamaRealtimeClientPong::Pong( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem) +{ + UNakamaRealtimeClientPong* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPong::Activate() +{ + static const TCHAR* TraceScope_Pong = TEXT("NakamaRTBP_Pong"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Pong); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + + StoredWebSocketSubsystem->Send(TEXT("pong"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// Party +UNakamaRealtimeClientParty* UNakamaRealtimeClientParty::Party( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + bool Open, + bool Hidden, + int32 MaxSize, + FNakamaRtUserPresence Self_, + FNakamaRtUserPresence Leader, + const TArray& Presences, + FString Label) +{ + UNakamaRealtimeClientParty* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredOpen = Open; + Action->StoredHidden = Hidden; + Action->StoredMaxSize = MaxSize; + Action->StoredSelf = Self_; + Action->StoredLeader = Leader; + Action->StoredPresences = Presences; + Action->StoredLabel = Label; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientParty::Activate() +{ + static const TCHAR* TraceScope_Party = TEXT("NakamaRTBP_Party"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Party); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetBoolField(TEXT("open"), StoredOpen); + Json->SetBoolField(TEXT("hidden"), StoredHidden); + Json->SetNumberField(TEXT("max_size"), StoredMaxSize); + Json->SetObjectField(TEXT("self"), StoredSelf.ToJson()); + Json->SetObjectField(TEXT("leader"), StoredLeader.ToJson()); + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + if (!StoredLabel.IsEmpty()) + { + Json->SetStringField(TEXT("label"), StoredLabel); + } + + StoredWebSocketSubsystem->Send(TEXT("party"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyCreate +UNakamaRealtimeClientPartyCreate* UNakamaRealtimeClientPartyCreate::PartyCreate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + bool Open, + int32 MaxSize, + FString Label, + bool Hidden) +{ + UNakamaRealtimeClientPartyCreate* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredOpen = Open; + Action->StoredMaxSize = MaxSize; + Action->StoredLabel = Label; + Action->StoredHidden = Hidden; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyCreate::Activate() +{ + static const TCHAR* TraceScope_PartyCreate = TEXT("NakamaRTBP_PartyCreate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyCreate); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + Json->SetBoolField(TEXT("open"), StoredOpen); + Json->SetNumberField(TEXT("max_size"), StoredMaxSize); + if (!StoredLabel.IsEmpty()) + { + Json->SetStringField(TEXT("label"), StoredLabel); + } + Json->SetBoolField(TEXT("hidden"), StoredHidden); + + StoredWebSocketSubsystem->Send(TEXT("party_create"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyJoin +UNakamaRealtimeClientPartyJoin* UNakamaRealtimeClientPartyJoin::PartyJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId) +{ + UNakamaRealtimeClientPartyJoin* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyJoin::Activate() +{ + static const TCHAR* TraceScope_PartyJoin = TEXT("NakamaRTBP_PartyJoin"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyJoin); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + + StoredWebSocketSubsystem->Send(TEXT("party_join"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyLeave +UNakamaRealtimeClientPartyLeave* UNakamaRealtimeClientPartyLeave::PartyLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId) +{ + UNakamaRealtimeClientPartyLeave* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyLeave::Activate() +{ + static const TCHAR* TraceScope_PartyLeave = TEXT("NakamaRTBP_PartyLeave"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyLeave); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + + StoredWebSocketSubsystem->Send(TEXT("party_leave"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyPromote +UNakamaRealtimeClientPartyPromote* UNakamaRealtimeClientPartyPromote::PartyPromote( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence) +{ + UNakamaRealtimeClientPartyPromote* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresence = Presence; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyPromote::Activate() +{ + static const TCHAR* TraceScope_PartyPromote = TEXT("NakamaRTBP_PartyPromote"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyPromote); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("party_promote"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyLeader +UNakamaRealtimeClientPartyLeader* UNakamaRealtimeClientPartyLeader::PartyLeader( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence) +{ + UNakamaRealtimeClientPartyLeader* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresence = Presence; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyLeader::Activate() +{ + static const TCHAR* TraceScope_PartyLeader = TEXT("NakamaRTBP_PartyLeader"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyLeader); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("party_leader"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyAccept +UNakamaRealtimeClientPartyAccept* UNakamaRealtimeClientPartyAccept::PartyAccept( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence) +{ + UNakamaRealtimeClientPartyAccept* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresence = Presence; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyAccept::Activate() +{ + static const TCHAR* TraceScope_PartyAccept = TEXT("NakamaRTBP_PartyAccept"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyAccept); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("party_accept"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyRemove +UNakamaRealtimeClientPartyRemove* UNakamaRealtimeClientPartyRemove::PartyRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence) +{ + UNakamaRealtimeClientPartyRemove* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresence = Presence; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyRemove::Activate() +{ + static const TCHAR* TraceScope_PartyRemove = TEXT("NakamaRTBP_PartyRemove"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyRemove); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + + StoredWebSocketSubsystem->Send(TEXT("party_remove"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyClose +UNakamaRealtimeClientPartyClose* UNakamaRealtimeClientPartyClose::PartyClose( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId) +{ + UNakamaRealtimeClientPartyClose* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyClose::Activate() +{ + static const TCHAR* TraceScope_PartyClose = TEXT("NakamaRTBP_PartyClose"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyClose); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + + StoredWebSocketSubsystem->Send(TEXT("party_close"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyJoinRequestList +UNakamaRealtimeClientPartyJoinRequestList* UNakamaRealtimeClientPartyJoinRequestList::PartyJoinRequestList( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId) +{ + UNakamaRealtimeClientPartyJoinRequestList* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyJoinRequestList::Activate() +{ + static const TCHAR* TraceScope_PartyJoinRequestList = TEXT("NakamaRTBP_PartyJoinRequestList"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyJoinRequestList); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + + StoredWebSocketSubsystem->Send(TEXT("party_join_request_list"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyJoinRequest +UNakamaRealtimeClientPartyJoinRequest* UNakamaRealtimeClientPartyJoinRequest::PartyJoinRequest( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + const TArray& Presences) +{ + UNakamaRealtimeClientPartyJoinRequest* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresences = Presences; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyJoinRequest::Activate() +{ + static const TCHAR* TraceScope_PartyJoinRequest = TEXT("NakamaRTBP_PartyJoinRequest"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyJoinRequest); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + if (StoredPresences.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredPresences) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("presences"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("party_join_request"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyMatchmakerAdd +UNakamaRealtimeClientPartyMatchmakerAdd* UNakamaRealtimeClientPartyMatchmakerAdd::PartyMatchmakerAdd( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + int32 MinCount, + int32 MaxCount, + FString Query, + int32 CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties) +{ + UNakamaRealtimeClientPartyMatchmakerAdd* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredMinCount = MinCount; + Action->StoredMaxCount = MaxCount; + Action->StoredQuery = Query; + Action->StoredCountMultiple = CountMultiple; + Action->StoredStringProperties = StringProperties; + Action->StoredNumericProperties = NumericProperties; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyMatchmakerAdd::Activate() +{ + static const TCHAR* TraceScope_PartyMatchmakerAdd = TEXT("NakamaRTBP_PartyMatchmakerAdd"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyMatchmakerAdd); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetNumberField(TEXT("min_count"), StoredMinCount); + Json->SetNumberField(TEXT("max_count"), StoredMaxCount); + if (!StoredQuery.IsEmpty()) + { + Json->SetStringField(TEXT("query"), StoredQuery); + } + if (StoredCountMultiple != 0) + { + Json->SetNumberField(TEXT("count_multiple"), StoredCountMultiple); + } + if (StoredStringProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredStringProperties) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("string_properties"), MapObj); + } + if (StoredNumericProperties.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : StoredNumericProperties) + { + MapObj->SetNumberField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("numeric_properties"), MapObj); + } + + StoredWebSocketSubsystem->Send(TEXT("party_matchmaker_add"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyMatchmakerRemove +UNakamaRealtimeClientPartyMatchmakerRemove* UNakamaRealtimeClientPartyMatchmakerRemove::PartyMatchmakerRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Ticket) +{ + UNakamaRealtimeClientPartyMatchmakerRemove* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredTicket = Ticket; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyMatchmakerRemove::Activate() +{ + static const TCHAR* TraceScope_PartyMatchmakerRemove = TEXT("NakamaRTBP_PartyMatchmakerRemove"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyMatchmakerRemove); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + if (!StoredTicket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), StoredTicket); + } + + StoredWebSocketSubsystem->Send(TEXT("party_matchmaker_remove"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyMatchmakerTicket +UNakamaRealtimeClientPartyMatchmakerTicket* UNakamaRealtimeClientPartyMatchmakerTicket::PartyMatchmakerTicket( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Ticket) +{ + UNakamaRealtimeClientPartyMatchmakerTicket* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredTicket = Ticket; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyMatchmakerTicket::Activate() +{ + static const TCHAR* TraceScope_PartyMatchmakerTicket = TEXT("NakamaRTBP_PartyMatchmakerTicket"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyMatchmakerTicket); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + if (!StoredTicket.IsEmpty()) + { + Json->SetStringField(TEXT("ticket"), StoredTicket); + } + + StoredWebSocketSubsystem->Send(TEXT("party_matchmaker_ticket"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyData +UNakamaRealtimeClientPartyData* UNakamaRealtimeClientPartyData::PartyData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence, + int64 OpCode, + const TArray& Data) +{ + UNakamaRealtimeClientPartyData* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredPresence = Presence; + Action->StoredOpCode = OpCode; + Action->StoredData = Data; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyData::Activate() +{ + static const TCHAR* TraceScope_PartyData = TEXT("NakamaRTBP_PartyData"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyData); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetObjectField(TEXT("presence"), StoredPresence.ToJson()); + Json->SetNumberField(TEXT("op_code"), StoredOpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(StoredData)); + + StoredWebSocketSubsystem->Send(TEXT("party_data"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyDataSend +UNakamaRealtimeClientPartyDataSend* UNakamaRealtimeClientPartyDataSend::PartyDataSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + int64 OpCode, + const TArray& Data) +{ + UNakamaRealtimeClientPartyDataSend* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredOpCode = OpCode; + Action->StoredData = Data; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyDataSend::Activate() +{ + static const TCHAR* TraceScope_PartyDataSend = TEXT("NakamaRTBP_PartyDataSend"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyDataSend); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + Json->SetNumberField(TEXT("op_code"), StoredOpCode); + Json->SetStringField(TEXT("data"), FBase64::Encode(StoredData)); + + StoredWebSocketSubsystem->Send(TEXT("party_data_send"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyPresenceEvent +UNakamaRealtimeClientPartyPresenceEvent* UNakamaRealtimeClientPartyPresenceEvent::PartyPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + const TArray& Joins, + const TArray& Leaves) +{ + UNakamaRealtimeClientPartyPresenceEvent* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredJoins = Joins; + Action->StoredLeaves = Leaves; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyPresenceEvent::Activate() +{ + static const TCHAR* TraceScope_PartyPresenceEvent = TEXT("NakamaRTBP_PartyPresenceEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyPresenceEvent); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + if (StoredJoins.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredJoins) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("joins"), Array); + } + if (StoredLeaves.Num() > 0) + { + TArray> Array; + for (const auto& Item : StoredLeaves) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("leaves"), Array); + } + + StoredWebSocketSubsystem->Send(TEXT("party_presence_event"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} + +// PartyUpdate +UNakamaRealtimeClientPartyUpdate* UNakamaRealtimeClientPartyUpdate::PartyUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Label, + bool Open, + bool Hidden) +{ + UNakamaRealtimeClientPartyUpdate* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; + Action->StoredPartyId = PartyId; + Action->StoredLabel = Label; + Action->StoredOpen = Open; + Action->StoredHidden = Hidden; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClientPartyUpdate::Activate() +{ + static const TCHAR* TraceScope_PartyUpdate = TEXT("NakamaRTBP_PartyUpdate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_PartyUpdate); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); + if (!StoredPartyId.IsEmpty()) + { + Json->SetStringField(TEXT("party_id"), StoredPartyId); + } + if (!StoredLabel.IsEmpty()) + { + Json->SetStringField(TEXT("label"), StoredLabel); + } + Json->SetBoolField(TEXT("open"), StoredOpen); + Json->SetBoolField(TEXT("hidden"), StoredHidden); + + StoredWebSocketSubsystem->Send(TEXT("party_update"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaClientBlueprintLibrary.h b/Nakama/Source/NakamaBlueprints/Public/NakamaClientBlueprintLibrary.h new file mode 100644 index 000000000..ddac7d190 --- /dev/null +++ b/Nakama/Source/NakamaBlueprints/Public/NakamaClientBlueprintLibrary.h @@ -0,0 +1,2896 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "NakamaTypes.h" +#include "NakamaApi.h" + +#include "NakamaClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaError, const FNakamaError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNakamaSuccess); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaSession, const FNakamaSession&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaGroup, const FNakamaGroup&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaAccount, const FNakamaAccount&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaUsers, const FNakamaUsers&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaValidatedSubscription, const FNakamaValidatedSubscription&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaMatchmakerStats, const FNakamaMatchmakerStats&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaChannelMessageList, const FNakamaChannelMessageList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaFriendList, const FNakamaFriendList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaFriendsOfFriendsList, const FNakamaFriendsOfFriendsList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaGroupList, const FNakamaGroupList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaGroupUserList, const FNakamaGroupUserList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaLeaderboardRecordList, const FNakamaLeaderboardRecordList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaMatchList, const FNakamaMatchList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaPartyList, const FNakamaPartyList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaNotificationList, const FNakamaNotificationList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaStorageObjectList, const FNakamaStorageObjectList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaSubscriptionList, const FNakamaSubscriptionList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaTournamentList, const FNakamaTournamentList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaTournamentRecordList, const FNakamaTournamentRecordList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaUserGroupList, const FNakamaUserGroupList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaStorageObjects, const FNakamaStorageObjects&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaRpc, const FNakamaRpc&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaValidatePurchaseResponse, const FNakamaValidatePurchaseResponse&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaValidateSubscriptionResponse, const FNakamaValidateSubscriptionResponse&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaLeaderboardRecord, const FNakamaLeaderboardRecord&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaStorageObjectAcks, const FNakamaStorageObjectAcks&, Result); + +// ============================================================================ +// Async Action Classes (one per RPC) +// ============================================================================ + +/** + * Add friends by ID or username to a user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAddFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AddFriends"), Category = "Nakama|Client") + static UNakamaClientAddFriends* AddFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + FString Metadata); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredIds; + TArray StoredUsernames; + FString StoredMetadata; +}; + +/** + * Add users to a group. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAddGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AddGroupUsers"), Category = "Nakama|Client") + static UNakamaClientAddGroupUsers* AddGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + TArray StoredUserIds; +}; + +/** + * Refresh a user's session using a refresh token retrieved from a previous authentication request. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientSessionRefresh : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "SessionRefresh"), Category = "Nakama|Client") + static UNakamaClientSessionRefresh* SessionRefresh( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientSessionLogout : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "SessionLogout"), Category = "Nakama|Client") + static UNakamaClientSessionLogout* SessionLogout( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + FString RefreshToken); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + FString StoredRefreshToken; +}; + +/** + * Authenticate a user with an Apple ID against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateApple : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateApple"), Category = "Nakama|Client") + static UNakamaClientAuthenticateApple* AuthenticateApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredToken; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with a custom id against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateCustom : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateCustom"), Category = "Nakama|Client") + static UNakamaClientAuthenticateCustom* AuthenticateCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Id, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredId; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with a device id against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateDevice : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateDevice"), Category = "Nakama|Client") + static UNakamaClientAuthenticateDevice* AuthenticateDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Id, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredId; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with an email+password against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateEmail : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateEmail"), Category = "Nakama|Client") + static UNakamaClientAuthenticateEmail* AuthenticateEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Email, + FString Password, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredEmail; + FString StoredPassword; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with a Facebook OAuth token against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateFacebook : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateFacebook"), Category = "Nakama|Client") + static UNakamaClientAuthenticateFacebook* AuthenticateFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredToken; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; + bool StoredSync = false; +}; + +/** + * Authenticate a user with a Facebook Instant Game token against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateFacebookInstantGame : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateFacebookInstantGame"), Category = "Nakama|Client") + static UNakamaClientAuthenticateFacebookInstantGame* AuthenticateFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString SignedPlayerInfo, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredSignedPlayerInfo; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with Apple's GameCenter against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateGameCenter : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateGameCenter"), Category = "Nakama|Client") + static UNakamaClientAuthenticateGameCenter* AuthenticateGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredPlayerId; + FString StoredBundleId; + int64 StoredTimestampSeconds = 0; + FString StoredSalt; + FString StoredSignature; + FString StoredPublicKeyUrl; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with Google against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateGoogle : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateGoogle"), Category = "Nakama|Client") + static UNakamaClientAuthenticateGoogle* AuthenticateGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredToken; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; +}; + +/** + * Authenticate a user with Steam against the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateSteam : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateSteam"), Category = "Nakama|Client") + static UNakamaClientAuthenticateSteam* AuthenticateSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + FString Token, + bool Create, + FString Username, + bool Sync, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FString StoredToken; + TMap StoredVars; + bool StoredCreate = false; + FString StoredUsername; + bool StoredSync = false; +}; + +/** + * Ban a set of users from a group. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientBanGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "BanGroupUsers"), Category = "Nakama|Client") + static UNakamaClientBanGroupUsers* BanGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + TArray StoredUserIds; +}; + +/** + * Block one or more users by ID or username. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientBlockFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "BlockFriends"), Category = "Nakama|Client") + static UNakamaClientBlockFriends* BlockFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredIds; + TArray StoredUsernames; +}; + +/** + * Create a new group with the current user as the owner. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientCreateGroup : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaGroup OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "CreateGroup"), Category = "Nakama|Client") + static UNakamaClientCreateGroup* CreateGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open, + int32 MaxCount); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredName; + FString StoredDescription; + FString StoredLangTag; + FString StoredAvatarUrl; + bool StoredOpen = false; + int32 StoredMaxCount = 0; +}; + +/** + * Delete the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteAccount : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteAccount"), Category = "Nakama|Client") + static UNakamaClientDeleteAccount* DeleteAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; +}; + +/** + * Delete one or more users by ID or username. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteFriends"), Category = "Nakama|Client") + static UNakamaClientDeleteFriends* DeleteFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredIds; + TArray StoredUsernames; +}; + +/** + * Delete a group by ID. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteGroup : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteGroup"), Category = "Nakama|Client") + static UNakamaClientDeleteGroup* DeleteGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; +}; + +/** + * Delete a leaderboard record. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteLeaderboardRecord : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteLeaderboardRecord"), Category = "Nakama|Client") + static UNakamaClientDeleteLeaderboardRecord* DeleteLeaderboardRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredLeaderboardId; +}; + +/** + * Delete one or more notifications for the current user. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteNotifications : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteNotifications"), Category = "Nakama|Client") + static UNakamaClientDeleteNotifications* DeleteNotifications( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredIds; +}; + +/** + * Delete a tournament record. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteTournamentRecord : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteTournamentRecord"), Category = "Nakama|Client") + static UNakamaClientDeleteTournamentRecord* DeleteTournamentRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredTournamentId; +}; + +/** + * Delete one or more objects by ID or username. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDeleteStorageObjects : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteStorageObjects"), Category = "Nakama|Client") + static UNakamaClientDeleteStorageObjects* DeleteStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& ObjectIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredObjectIds; +}; + +/** + * Submit an event for processing in the server's registered runtime custom events handler. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Event"), Category = "Nakama|Client") + static UNakamaClientEvent* Event( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Timestamp, + bool External, + const TMap& Properties); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredName; + FString StoredTimestamp; + bool StoredExternal = false; + TMap StoredProperties; +}; + +/** + * Fetch the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientGetAccount : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaAccount OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetAccount"), Category = "Nakama|Client") + static UNakamaClientGetAccount* GetAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; +}; + +/** + * Fetch zero or more users by ID and/or username. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientGetUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaUsers OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetUsers"), Category = "Nakama|Client") + static UNakamaClientGetUsers* GetUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Ids, + const TArray& Usernames, + const TArray& FacebookIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredIds; + TArray StoredUsernames; + TArray StoredFacebookIds; +}; + +/** + * Get subscription by product id. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientGetSubscription : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidatedSubscription OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetSubscription"), Category = "Nakama|Client") + static UNakamaClientGetSubscription* GetSubscription( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString ProductId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredProductId; +}; + +/** + * Get matchmaker stats. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientGetMatchmakerStats : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaMatchmakerStats OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetMatchmakerStats"), Category = "Nakama|Client") + static UNakamaClientGetMatchmakerStats* GetMatchmakerStats( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; +}; + +/** + * A healthcheck which load balancers can use to check the service. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientHealthcheck : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Healthcheck"), Category = "Nakama|Client") + static UNakamaClientHealthcheck* Healthcheck( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; +}; + +/** + * Import Facebook friends and add them to a user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientImportFacebookFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ImportFacebookFriends"), Category = "Nakama|Client") + static UNakamaClientImportFacebookFriends* ImportFacebookFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; + bool StoredReset = false; +}; + +/** + * Import Steam friends and add them to a user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientImportSteamFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ImportSteamFriends"), Category = "Nakama|Client") + static UNakamaClientImportSteamFriends* ImportSteamFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Reset, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; + bool StoredReset = false; +}; + +/** + * Immediately join an open group, or request to join a closed one. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientJoinGroup : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "JoinGroup"), Category = "Nakama|Client") + static UNakamaClientJoinGroup* JoinGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; +}; + +/** + * Attempt to join an open and running tournament. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientJoinTournament : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "JoinTournament"), Category = "Nakama|Client") + static UNakamaClientJoinTournament* JoinTournament( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredTournamentId; +}; + +/** + * Kick a set of users from a group. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientKickGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "KickGroupUsers"), Category = "Nakama|Client") + static UNakamaClientKickGroupUsers* KickGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + TArray StoredUserIds; +}; + +/** + * Leave a group the user is a member of. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLeaveGroup : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LeaveGroup"), Category = "Nakama|Client") + static UNakamaClientLeaveGroup* LeaveGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; +}; + +/** + * Add an Apple ID to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkApple : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkApple"), Category = "Nakama|Client") + static UNakamaClientLinkApple* LinkApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Add a custom ID to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkCustom : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkCustom"), Category = "Nakama|Client") + static UNakamaClientLinkCustom* LinkCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredId; + TMap StoredVars; +}; + +/** + * Add a device ID to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkDevice : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkDevice"), Category = "Nakama|Client") + static UNakamaClientLinkDevice* LinkDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredId; + TMap StoredVars; +}; + +/** + * Add an email+password to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkEmail : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkEmail"), Category = "Nakama|Client") + static UNakamaClientLinkEmail* LinkEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredEmail; + FString StoredPassword; + TMap StoredVars; +}; + +/** + * Add Facebook to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkFacebook : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkFacebook"), Category = "Nakama|Client") + static UNakamaClientLinkFacebook* LinkFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; + bool StoredSync = false; +}; + +/** + * Add Facebook Instant Game to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkFacebookInstantGame : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkFacebookInstantGame"), Category = "Nakama|Client") + static UNakamaClientLinkFacebookInstantGame* LinkFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredSignedPlayerInfo; + TMap StoredVars; +}; + +/** + * Add Apple's GameCenter to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkGameCenter : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkGameCenter"), Category = "Nakama|Client") + static UNakamaClientLinkGameCenter* LinkGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredPlayerId; + FString StoredBundleId; + int64 StoredTimestampSeconds = 0; + FString StoredSalt; + FString StoredSignature; + FString StoredPublicKeyUrl; + TMap StoredVars; +}; + +/** + * Add Google to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkGoogle : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkGoogle"), Category = "Nakama|Client") + static UNakamaClientLinkGoogle* LinkGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Add Steam to the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientLinkSteam : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "LinkSteam"), Category = "Nakama|Client") + static UNakamaClientLinkSteam* LinkSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + bool Sync, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; + bool StoredSync = false; +}; + +/** + * List a channel's message history. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListChannelMessages : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaChannelMessageList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListChannelMessages"), Category = "Nakama|Client") + static UNakamaClientListChannelMessages* ListChannelMessages( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString ChannelId, + int32 Limit, + bool Forward, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredChannelId; + int32 StoredLimit = 0; + bool StoredForward = false; + FString StoredCursor; +}; + +/** + * List all friends for the current user. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaFriendList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListFriends"), Category = "Nakama|Client") + static UNakamaClientListFriends* ListFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + int32 State, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + int32 StoredState = 0; + FString StoredCursor; +}; + +/** + * List friends of friends for the current user. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListFriendsOfFriends : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaFriendsOfFriendsList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListFriendsOfFriends"), Category = "Nakama|Client") + static UNakamaClientListFriendsOfFriends* ListFriendsOfFriends( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + FString StoredCursor; +}; + +/** + * List groups based on given filters. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListGroups : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaGroupList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListGroups"), Category = "Nakama|Client") + static UNakamaClientListGroups* ListGroups( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Name, + FString Cursor, + int32 Limit, + FString LangTag, + int32 Members, + bool Open); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredName; + FString StoredCursor; + int32 StoredLimit = 0; + FString StoredLangTag; + int32 StoredMembers = 0; + bool StoredOpen = false; +}; + +/** + * List all users that are part of a group. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaGroupUserList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListGroupUsers"), Category = "Nakama|Client") + static UNakamaClientListGroupUsers* ListGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + int32 Limit, + int32 State, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + int32 StoredLimit = 0; + int32 StoredState = 0; + FString StoredCursor; +}; + +/** + * List leaderboard records. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListLeaderboardRecords : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaLeaderboardRecordList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListLeaderboardRecords"), Category = "Nakama|Client") + static UNakamaClientListLeaderboardRecords* ListLeaderboardRecords( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredLeaderboardId; + TArray StoredOwnerIds; + int32 StoredLimit = 0; + FString StoredCursor; + int64 StoredExpiry = 0; +}; + +/** + * List leaderboard records around the target ownerId. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListLeaderboardRecordsAroundOwner : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaLeaderboardRecordList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListLeaderboardRecordsAroundOwner"), Category = "Nakama|Client") + static UNakamaClientListLeaderboardRecordsAroundOwner* ListLeaderboardRecordsAroundOwner( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredLeaderboardId; + int32 StoredLimit = 0; + FString StoredOwnerId; + int64 StoredExpiry = 0; + FString StoredCursor; +}; + +/** + * List running matches and optionally filter by matching criteria. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListMatches : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaMatchList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListMatches"), Category = "Nakama|Client") + static UNakamaClientListMatches* ListMatches( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + bool Authoritative, + FString Label, + int32 MinSize, + int32 MaxSize, + FString Query); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + bool StoredAuthoritative = false; + FString StoredLabel; + int32 StoredMinSize = 0; + int32 StoredMaxSize = 0; + FString StoredQuery; +}; + +/** + * List parties and optionally filter by matching criteria. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListParties : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaPartyList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListParties"), Category = "Nakama|Client") + static UNakamaClientListParties* ListParties( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + bool Open, + FString Query, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + bool StoredOpen = false; + FString StoredQuery; + FString StoredCursor; +}; + +/** + * Fetch list of notifications. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListNotifications : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaNotificationList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListNotifications"), Category = "Nakama|Client") + static UNakamaClientListNotifications* ListNotifications( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString CacheableCursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + FString StoredCacheableCursor; +}; + +/** + * List publicly readable storage objects in a given collection. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListStorageObjects : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaStorageObjectList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListStorageObjects"), Category = "Nakama|Client") + static UNakamaClientListStorageObjects* ListStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString UserId, + FString Collection, + int32 Limit, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredUserId; + FString StoredCollection; + int32 StoredLimit = 0; + FString StoredCursor; +}; + +/** + * List user's subscriptions. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListSubscriptions : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSubscriptionList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListSubscriptions"), Category = "Nakama|Client") + static UNakamaClientListSubscriptions* ListSubscriptions( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 Limit, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredLimit = 0; + FString StoredCursor; +}; + +/** + * List current or upcoming tournaments. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListTournaments : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaTournamentList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListTournaments"), Category = "Nakama|Client") + static UNakamaClientListTournaments* ListTournaments( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + int32 CategoryStart, + int32 CategoryEnd, + int32 StartTime, + int32 EndTime, + int32 Limit, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + int32 StoredCategoryStart = 0; + int32 StoredCategoryEnd = 0; + int32 StoredStartTime = 0; + int32 StoredEndTime = 0; + int32 StoredLimit = 0; + FString StoredCursor; +}; + +/** + * List tournament records. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListTournamentRecords : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaTournamentRecordList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListTournamentRecords"), Category = "Nakama|Client") + static UNakamaClientListTournamentRecords* ListTournamentRecords( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + const TArray& OwnerIds, + int32 Limit, + FString Cursor, + int64 Expiry); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredTournamentId; + TArray StoredOwnerIds; + int32 StoredLimit = 0; + FString StoredCursor; + int64 StoredExpiry = 0; +}; + +/** + * List tournament records for a given owner. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListTournamentRecordsAroundOwner : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaTournamentRecordList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListTournamentRecordsAroundOwner"), Category = "Nakama|Client") + static UNakamaClientListTournamentRecordsAroundOwner* ListTournamentRecordsAroundOwner( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + int32 Limit, + FString OwnerId, + int64 Expiry, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredTournamentId; + int32 StoredLimit = 0; + FString StoredOwnerId; + int64 StoredExpiry = 0; + FString StoredCursor; +}; + +/** + * List groups the current user belongs to. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientListUserGroups : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaUserGroupList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListUserGroups"), Category = "Nakama|Client") + static UNakamaClientListUserGroups* ListUserGroups( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString UserId, + int32 Limit, + int32 State, + FString Cursor); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredUserId; + int32 StoredLimit = 0; + int32 StoredState = 0; + FString StoredCursor; +}; + +/** + * Promote a set of users in a group to the next role up. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientPromoteGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PromoteGroupUsers"), Category = "Nakama|Client") + static UNakamaClientPromoteGroupUsers* PromoteGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + TArray StoredUserIds; +}; + +/** + * Demote a set of users in a group to the next role down. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientDemoteGroupUsers : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DemoteGroupUsers"), Category = "Nakama|Client") + static UNakamaClientDemoteGroupUsers* DemoteGroupUsers( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + const TArray& UserIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + TArray StoredUserIds; +}; + +/** + * Get storage objects. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientReadStorageObjects : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaStorageObjects OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ReadStorageObjects"), Category = "Nakama|Client") + static UNakamaClientReadStorageObjects* ReadStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& ObjectIds); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredObjectIds; +}; + +/** + * Execute a Lua function on the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientRpcFunc : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRpc OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "RpcFunc"), Category = "Nakama|Client") + static UNakamaClientRpcFunc* RpcFunc( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Payload, + FString HttpKey); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredId; + TMap StoredPayload; + FString StoredHttpKey; +}; + +/** + * Remove the Apple ID from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkApple : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkApple"), Category = "Nakama|Client") + static UNakamaClientUnlinkApple* UnlinkApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Remove the custom ID from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkCustom : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkCustom"), Category = "Nakama|Client") + static UNakamaClientUnlinkCustom* UnlinkCustom( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredId; + TMap StoredVars; +}; + +/** + * Remove the device ID from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkDevice : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkDevice"), Category = "Nakama|Client") + static UNakamaClientUnlinkDevice* UnlinkDevice( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Id, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredId; + TMap StoredVars; +}; + +/** + * Remove the email+password from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkEmail : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkEmail"), Category = "Nakama|Client") + static UNakamaClientUnlinkEmail* UnlinkEmail( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Email, + FString Password, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredEmail; + FString StoredPassword; + TMap StoredVars; +}; + +/** + * Remove Facebook from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkFacebook : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkFacebook"), Category = "Nakama|Client") + static UNakamaClientUnlinkFacebook* UnlinkFacebook( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Remove Facebook Instant Game profile from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkFacebookInstantGame : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkFacebookInstantGame"), Category = "Nakama|Client") + static UNakamaClientUnlinkFacebookInstantGame* UnlinkFacebookInstantGame( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedPlayerInfo, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredSignedPlayerInfo; + TMap StoredVars; +}; + +/** + * Remove Apple's GameCenter from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkGameCenter : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkGameCenter"), Category = "Nakama|Client") + static UNakamaClientUnlinkGameCenter* UnlinkGameCenter( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString PlayerId, + FString BundleId, + int64 TimestampSeconds, + FString Salt, + FString Signature, + FString PublicKeyUrl, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredPlayerId; + FString StoredBundleId; + int64 StoredTimestampSeconds = 0; + FString StoredSalt; + FString StoredSignature; + FString StoredPublicKeyUrl; + TMap StoredVars; +}; + +/** + * Remove Google from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkGoogle : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkGoogle"), Category = "Nakama|Client") + static UNakamaClientUnlinkGoogle* UnlinkGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Remove Steam from the social profiles on the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUnlinkSteam : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UnlinkSteam"), Category = "Nakama|Client") + static UNakamaClientUnlinkSteam* UnlinkSteam( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Token, + const TMap& Vars); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredToken; + TMap StoredVars; +}; + +/** + * Update fields in the current user's account. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUpdateAccount : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UpdateAccount"), Category = "Nakama|Client") + static UNakamaClientUpdateAccount* UpdateAccount( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Username, + FString DisplayName, + FString AvatarUrl, + FString LangTag, + FString Location, + FString Timezone); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredUsername; + FString StoredDisplayName; + FString StoredAvatarUrl; + FString StoredLangTag; + FString StoredLocation; + FString StoredTimezone; +}; + +/** + * Update fields in a given group. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientUpdateGroup : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UpdateGroup"), Category = "Nakama|Client") + static UNakamaClientUpdateGroup* UpdateGroup( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString GroupId, + FString Name, + FString Description, + FString LangTag, + FString AvatarUrl, + bool Open); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredGroupId; + FString StoredName; + FString StoredDescription; + FString StoredLangTag; + FString StoredAvatarUrl; + bool StoredOpen = false; +}; + +/** + * Validate Apple IAP Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidatePurchaseApple : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidatePurchaseResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidatePurchaseApple"), Category = "Nakama|Client") + static UNakamaClientValidatePurchaseApple* ValidatePurchaseApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredReceipt; + bool StoredPersist = false; +}; + +/** + * Validate Apple Subscription Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidateSubscriptionApple : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidateSubscriptionResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidateSubscriptionApple"), Category = "Nakama|Client") + static UNakamaClientValidateSubscriptionApple* ValidateSubscriptionApple( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredReceipt; + bool StoredPersist = false; +}; + +/** + * Validate Google IAP Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidatePurchaseGoogle : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidatePurchaseResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidatePurchaseGoogle"), Category = "Nakama|Client") + static UNakamaClientValidatePurchaseGoogle* ValidatePurchaseGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Purchase, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredPurchase; + bool StoredPersist = false; +}; + +/** + * Validate Google Subscription Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidateSubscriptionGoogle : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidateSubscriptionResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidateSubscriptionGoogle"), Category = "Nakama|Client") + static UNakamaClientValidateSubscriptionGoogle* ValidateSubscriptionGoogle( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Receipt, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredReceipt; + bool StoredPersist = false; +}; + +/** + * Validate Huawei IAP Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidatePurchaseHuawei : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidatePurchaseResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidatePurchaseHuawei"), Category = "Nakama|Client") + static UNakamaClientValidatePurchaseHuawei* ValidatePurchaseHuawei( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString Purchase, + FString Signature, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredPurchase; + FString StoredSignature; + bool StoredPersist = false; +}; + +/** + * Validate FB Instant IAP Receipt + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientValidatePurchaseFacebookInstant : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaValidatePurchaseResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ValidatePurchaseFacebookInstant"), Category = "Nakama|Client") + static UNakamaClientValidatePurchaseFacebookInstant* ValidatePurchaseFacebookInstant( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString SignedRequest, + bool Persist); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredSignedRequest; + bool StoredPersist = false; +}; + +/** + * Write a record to a leaderboard. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientWriteLeaderboardRecord : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaLeaderboardRecord OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "WriteLeaderboardRecord"), Category = "Nakama|Client") + static UNakamaClientWriteLeaderboardRecord* WriteLeaderboardRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString LeaderboardId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredLeaderboardId; + int64 StoredScore = 0; + int64 StoredSubscore = 0; + FString StoredMetadata; + ENakamaOperator StoredOperator = static_cast(0); +}; + +/** + * Write objects into the storage engine. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientWriteStorageObjects : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaStorageObjectAcks OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "WriteStorageObjects"), Category = "Nakama|Client") + static UNakamaClientWriteStorageObjects* WriteStorageObjects( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + const TArray& Objects); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + TArray StoredObjects; +}; + +/** + * Write a record to a tournament. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClientWriteTournamentRecord : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaLeaderboardRecord OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "WriteTournamentRecord"), Category = "Nakama|Client") + static UNakamaClientWriteTournamentRecord* WriteTournamentRecord( + UObject* WorldContextObject, + FNakamaClientConfig Client, + const FNakamaSession& Session, + FString TournamentId, + int64 Score, + int64 Subscore, + FString Metadata, + ENakamaOperator Operator); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + FNakamaSession Session; + FString StoredTournamentId; + int64 StoredScore = 0; + int64 StoredSubscore = 0; + FString StoredMetadata; + ENakamaOperator StoredOperator = static_cast(0); +}; diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaClientRequests.h b/Nakama/Source/NakamaBlueprints/Public/NakamaClientRequests.h deleted file mode 100644 index 0e2637bf6..000000000 --- a/Nakama/Source/NakamaBlueprints/Public/NakamaClientRequests.h +++ /dev/null @@ -1,3120 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "Kismet/BlueprintAsyncActionBase.h" -#include "Delegates/DelegateCombinations.h" -#include "NakamaClient.h" -#include "NakamaStorageObject.h" -#include "NakamaError.h" -#include "NakamaGroup.h" -#include "NakamaMatch.h" -#include "NakamaFriend.h" -#include "NakamaNotification.h" -#include "NakamaRPC.h" -#include "NakamaChannelTypes.h" -#include "NakamaLeaderboard.h" -#include "NakamaTournament.h" - -#include "NakamaClientRequests.generated.h" - -// Global Delegates for empty success / error -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnyError, FNakamaError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSuccessful); - -// --- Authentication --- // - -/** - * Authenticate Custom - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateCustom, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateCustom : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateCustom OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateCustom OnError; - - /** - * Authenticate a user with a custom id. - * @param Client The Client to use. - * @param UserID A custom identifier usually obtained from an external authentication service. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateCustom* AuthenticateCustom(UNakamaClient *Client, FString UserID, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString UserID; - FString Username; - bool bCreateAccount; - TMap Vars; - -}; - - -/** - * Authenticate Email - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateEmail, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateEmail : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateEmail OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateEmail OnError; - - - /** - * Authenticate a user with an email and password. - * - * @param Client The Client to use. - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateEmail* AuthenticateEmail(UNakamaClient *Client, FString Email, FString Password, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString Email; - FString Password; - FString Username; - bool bCreateAccount; - TMap Vars; - -}; - -/** - * Authenticate Device - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateDevice, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateDevice : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateDevice OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateDevice OnError; - - /** - * Authenticate a user with a device id. - * - * @param Client The Client to use. - * @param DeviceID A device identifier usually obtained from a platform API. - * @param Username A username used to create the user. Defaults to empty string. - * @param CreateAccount True if the user should be created when authenticated. Defaults to false. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateDevice* AuthenticateDevice(UNakamaClient *Client, FString DeviceID, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString DeviceID; - FString Username; - bool bCreateAccount; - TMap Vars; - -}; - - -/** - * Authenticate Steam - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateSteam, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateSteam : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateSteam OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateSteam OnError; - - /** - * Authenticate a user with a Steam auth token. - * - * @param Client The Client to use. - * @param SteamToken An authentication token from the Steam network. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param ImportFriends True if the Steam friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateSteam* AuthenticateSteam(UNakamaClient *Client, FString SteamToken, FString Username, bool CreateAccount, bool ImportFriends, TMap Vars); - - virtual void Activate() override; - -private: - - FString SteamToken; - FString Username; - bool bCreateAccount; - bool bImportFriends; - TMap Vars; - -}; - - -/** - * Authenticate Google - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateGoogle, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateGoogle : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateGoogle OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateGoogle OnError; - - /** - * Authenticate a user with a Google auth token. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Google SDK. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateGoogle* AuthenticateGoogle(UNakamaClient *Client, FString AccessToken, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString AccessToken; - FString Username; - bool bCreateAccount; - TMap Vars; - -}; - - -/** - * Authenticate Game Center - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateGameCenter, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateGameCenter: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateGameCenter OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateGameCenter OnError; - - /** - * Authenticate a user with Apple Game Center. - * - * @param Client The Client to use. - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateGameCenter* AuthenticateGameCenter(UNakamaClient *Client, FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, FString Signature, FString PublicKeyUrl, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString PlayerId; - FString BundleId; - int64 TimeStampSeconds; - FString Salt; - FString Signature; - FString PublicKeyUrl; - FString Username; - bool bCreateAccount; - TMap Vars; - -}; - - -/** - * Authenticate Facebook - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateFacebook, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateFacebook: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateFacebook OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateFacebook OnError; - - /** - * Authenticate a user with a Facebook auth token. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param ImportFriends True if the Facebook friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateFacebook* AuthenticateFacebook(UNakamaClient *Client, FString AccessToken, FString Username, bool CreateAccount, bool ImportFriends, TMap Vars); - - virtual void Activate() override; - -private: - - FString AccessToken; - FString Username; - bool ImportFriends; - bool bCreateAccount; - TMap Vars; - -}; - - -/** - * Authenticate Apple - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateApple, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateApple: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateApple OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateApple OnError; - - /** - * Authenticate a user with Apple Sign In. - * - * @param Client The Client to use. - * @param Token The ID token received from Apple to validate. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Vars")) - static UNakamaClientAuthenticateApple* AuthenticateApple(UNakamaClient *Client, FString Token, FString Username, bool CreateAccount, TMap Vars); - - virtual void Activate() override; - -private: - - FString Token; - FString Username; - bool ImportFriends; - bool bCreateAccount; - TMap Vars; - -}; - -/** - * Authenticate Refresh (using session refresh token) - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAuthenticateRefresh, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAuthenticateRefresh: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateRefresh OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnAuthenticateRefresh OnError; - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param Client The Client to use. - * @param Session The session of the user. - **/ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientAuthenticateRefresh* AuthenticateRefresh(UNakamaClient *Client, UNakamaSession* Session); - - virtual void Activate() override; - -}; - - -// --- Linking --- // - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLinkError, FNakamaError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLinkSuccessful); // Renamed - -/** - * Link Custom - */ - - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkCustom: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * - * Link a custom id to the user account owned by the session. - * - * @param Client The Client to use. - * @param CustomId A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkCustom* LinkCustom(UNakamaClient *Client, UNakamaSession *Session, FString CustomId); - - virtual void Activate() override; - -private: - - FString CustomId; - -}; - -/** - * Link Device - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkDevice: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link a device id to the user account owned by the session. - * - * @param Client The Client to use. - * @param DeviceId A device identifier usually obtained from a platform API. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkDevice* LinkDevice(UNakamaClient *Client, UNakamaSession *Session, FString DeviceId); - - virtual void Activate() override; - -private: - - FString DeviceId; - -}; - -/** - * Link Email - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkEmail: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link an email with password to the user account owned by the session. - * - * @param Client The Client to use. - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkEmail* LinkEmail(UNakamaClient *Client, UNakamaSession *Session, FString Email, FString Password); - - virtual void Activate() override; - -private: - - FString Email; - FString Password; - -}; - - -/** - * Link Facebook - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkFacebook: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link a Facebook profile to a user account. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param ImportFriends True if the Facebook friends should be imported. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkFacebook* LinkFacebook(UNakamaClient *Client, UNakamaSession *Session, FString AccessToken, bool ImportFriends); - - virtual void Activate() override; - -private: - - FString AccessToken; - bool ImportFriends; - -}; - - -/** - * Link GameCenter - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkGameCenter : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link a Game Center profile to a user account. - * - * @param Client The Client to use. - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkGameCenter* LinkGameCenter(UNakamaClient *Client, UNakamaSession *Session, FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, FString Signature, FString PublicKeyUrl); - - virtual void Activate() override; - -private: - - FString PlayerId; - FString BundleId; - int64 TimeStampSeconds; - FString Salt; - FString Signature; - FString PublicKeyUrl; - -}; - - -/** - * Link Google - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkGoogle: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link a Google profile to a user account. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Google SDK. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkGoogle* LinkGoogle(UNakamaClient *Client, UNakamaSession *Session, FString AccessToken); - - virtual void Activate() override; - -private: - - FString AccessToken; - -}; - - -/** - * Link Steam - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkSteam: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link a Steam profile to a user account. - * - * @param Client The Client to use. - * @param SteamToken An authentication token from the Steam network. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkSteam* LinkSteam(UNakamaClient *Client, UNakamaSession *Session, FString SteamToken); - - virtual void Activate() override; - -private: - - FString SteamToken; - -}; - - -/** - * Link Apple - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLinkApple: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Link an Apple ID to the social profiles on the current user's account. - * - * @param Client The Client to use. - * @param Token The ID token received from Apple. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Link", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLinkApple* LinkApple(UNakamaClient *Client, UNakamaSession *Session, FString Token); - - virtual void Activate() override; - -private: - - FString Token; - -}; - - -// --- Unlinking --- // - - -/** - * UnLink Custom - */ - - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkCustom: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a custom id from the user account owned by the session. - * - * @param Client The Client to use. - * @param CustomId A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user. - */ - - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkCustom* UnLinkCustom(UNakamaClient *Client, UNakamaSession *Session, FString CustomId); - - virtual void Activate() override; - -private: - - FString CustomId; - -}; - -/** - * UnLink Device - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkDevice: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a device id from the user account owned by the session. - * - * @param Client The Client to use. - * @param DeviceId A device identifier usually obtained from a platform API. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkDevice* UnLinkDevice(UNakamaClient *Client, UNakamaSession *Session, FString DeviceId); - - virtual void Activate() override; - -private: - - FString DeviceId; - -}; - -/** - * UnLink Email - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkEmail: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink an email with password from the user account owned by the session. - * - * @param Client The Client to use. - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkEmail* UnLinkEmail(UNakamaClient *Client, UNakamaSession *Session, FString Email, FString Password); - - virtual void Activate() override; - -private: - - FString Email; - FString Password; - -}; - - -/** - * UnLink Facebook - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkFacebook: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a Facebook profile from the user account owned by the session. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkFacebook* UnLinkFacebook(UNakamaClient *Client, UNakamaSession *Session, FString AccessToken); - - virtual void Activate() override; - -private: - - FString AccessToken; - -}; - - -/** - * UnLink GameCenter - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkGameCenter : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a Game Center profile from the user account owned by the session. - * - * @param Client The Client to use. - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkGameCenter* UnLinkGameCenter(UNakamaClient *Client, UNakamaSession *Session, FString PlayerId, FString BundleId, int64 TimeStampSeconds, FString Salt, FString Signature, FString PublicKeyUrl); - - virtual void Activate() override; - -private: - - FString PlayerId; - FString BundleId; - int64 TimeStampSeconds; - FString Salt; - FString Signature; - FString PublicKeyUrl; - -}; - - -/** - * UnLink Google - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkGoogle: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a Google profile from the user account owned by the session. - * - * @param Client The Client to use. - * @param AccessToken An OAuth access token from the Google SDK. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkGoogle* UnLinkGoogle(UNakamaClient *Client, UNakamaSession *Session, FString AccessToken); - - virtual void Activate() override; - -private: - - FString AccessToken; - -}; - - -/** - * UnLink Steam - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkSteam: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a Steam profile from the user account owned by the session. - * - * @param Client The Client to use. - * @param SteamToken An authentication token from the Steam network. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkSteam* UnLinkSteam(UNakamaClient *Client, UNakamaSession *Session, FString SteamToken); - - virtual void Activate() override; - -private: - - FString SteamToken; - -}; - - -/** - * UnLink Apple - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUnLinkApple: public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnLinkError OnError; - - UPROPERTY(BlueprintAssignable) - FOnLinkSuccessful OnSuccess; - - /** - * Unlink a Apple profile from the user account owned by the session. - * - * @param Client The Client to use. - * @param Token An Apple authentication token. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|UnLink", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUnLinkApple* UnLinkApple(UNakamaClient *Client, UNakamaSession *Session, FString Token); - - virtual void Activate() override; - -private: - - FString Token; - -}; - -// --- Functions --- // - -/** - * Refresh Session - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRefreshSession, FNakamaError, Error, UNakamaSession*, Session); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientRefreshSession : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnRefreshSession OnError; - - UPROPERTY(BlueprintAssignable) - FOnRefreshSession OnSuccess; - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param Session The session of the user. - * @param Client The Client to use. - **/ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Refresh", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientRefreshSession* RefreshSession(UNakamaClient *Client, UNakamaSession *Session); - - virtual void Activate() override; - -private: - - FString Token; - -}; - -/** - * Import Facebook Friends - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientImportFacebookFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - /** - * Import Facebook friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Facebook. This function can be used to be - * explicit with the import operation. - * - * @param Client The Client to use. - * @param Session The session of the user. - * @param Token An OAuth access token from the Facebook SDK. - * @param Reset True if the Facebook friend import for the user should be reset. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientImportFacebookFriends* ImportFacebookFriends(UNakamaClient *Client, UNakamaSession *Session, FString Token, bool Reset); - - virtual void Activate() override; - -private: - - FString Token; - bool Reset; - -}; - -/** - * Import Steam Friends - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientImportSteamFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - /** - * Import Steam friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Steam. This function can be used to be - * explicit with the import operation. - * - * @param Client The Client to use. - * @param Session The session of the user. - * @param SteamToken The Steam token to use. - * @param Reset True if the Steam friend import for the user should be reset. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientImportSteamFriends* ImportSteamFriends(UNakamaClient *Client, UNakamaSession *Session, FString SteamToken, bool Reset); - - virtual void Activate() override; - -private: - - FString SteamToken; - bool Reset; - -}; - - -// --- Account and User Info --- // - -/** - * Get User Account - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGetUserAccount, FNakamaError, Error, FNakamaAccount, Account); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientGetUserAccount : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnGetUserAccount OnError; - - UPROPERTY(BlueprintAssignable) - FOnGetUserAccount OnSuccess; - - /** - * Fetch the user account owned by the session. - * - * @param Client The Client to use. - * @param Session The session of the user. - */ - - UFUNCTION(BlueprintCallable, Category = "Nakama|Users", meta = (BlueprintInternalUseOnly = "true", DisplayName = "Get Account")) - static UNakamaClientGetUserAccount* GetUserAccount(UNakamaClient *Client, UNakamaSession *Session); - - virtual void Activate() override; -}; - - -/** - * Get Users - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGetUsers, FNakamaError, Error, FNakamaUserList, Users); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientGetUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FGetUsers OnSuccess; - - UPROPERTY(BlueprintAssignable) - FGetUsers OnError; - - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param Client The Client to use. - * @param UserIds List of user IDs.f - * @param Usernames List of usernames. - * @param FacebookIds List of Facebook IDs. - * @param Session The session of the user. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Users", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientGetUsers* GetUsers(UNakamaClient *Client, UNakamaSession *Session, TArray UserIds, TArray Usernames, TArray FacebookIds); - - virtual void Activate() override; - -private: - - TArray UserIds; - TArray Usernames; - TArray FacebookIds; -}; - - - -/** - * Update Account - */ - - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUpdateAccount : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - /** - * Update the current user's account on the server. - * - * @param Username The new username for the user. - * @param DisplayName A new display name for the user. - * @param AvatarUrl A new avatar url for the user. - * @param LanguageTag A new language tag in BCP-47 format for the user. - * @param Location A new location for the user. - * @param Timezone New timezone information for the user. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Users", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUpdateAccount* UpdateAccount(UNakamaClient *Client, UNakamaSession *Session, FString Username, FString DisplayName, FString AvatarUrl, FString LanguageTag, FString Location, FString Timezone); - - virtual void Activate() override; - -private: - - FString Username; - FString DisplayName; - FString AvatarUrl; - FString LanguageTag; - FString Location; - FString Timezone; -}; - -/** - * List Matches - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListMatches, FNakamaError, Error, FNakamaMatchList, MatchList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListMatches : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListMatches OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListMatches OnError; - - /** - * Fetch a list of matches active on the server. - * - * @param Client The Client to use. - * @param Session The session of the user. - * @param MinSize The minimum number of match participants. - * @param MaxSize The maximum number of match participants. - * @param Limit The number of matches to list. - * @param Label The label to filter the match list on. - * @param Query The query to the match listing. - * @param Authoritative True to include authoritative matches. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListMatches* ListMatches(UNakamaClient* Client, UNakamaSession *Session, int32 MinSize, int32 MaxSize, int32 Limit, FString Label, FString Query, bool Authoritative); - - virtual void Activate() override; - -private: - - int32 MinSize; - int32 MaxSize; - int32 Limit; - FString Label; - FString Query; - bool Authoritative; - -}; - -// --- Friends --- // - -/** - * Get Friends - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListFriends, FNakamaError, Error, FNakamaFriendList, FriendsList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientGetFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListFriends OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListFriends OnError; - - /** - * List of friends of the current user. - * - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true", DisplayName = "List Friends")) - static UNakamaClientGetFriends* GetFriends(UNakamaClient* Client, UNakamaSession *Session, int32 Limit, ENakamaFriendState State, FString Cursor); - - virtual void Activate() override; - -private: - int32 Limit; - ENakamaFriendState State; - FString Cursor; - -}; - - -/** - * Add Friends - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAddFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - /** - * Add one or more friends by id. - * - * @param Ids The ids of the users to add or invite as friends. - * @param Usernames The usernames of the users to add as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientAddFriends* AddFriends(UNakamaClient* Client, UNakamaSession *Session, TArray Ids, TArray Usernames); - - virtual void Activate() override; - -private: - TArray Ids; - TArray Usernames; -}; - - - -/** - * Remove Friends - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientRemoveFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Delete one more or users by id or username from friends. - * - * @param Ids the user ids to remove as friends. - * @param Usernames The usernames to remove as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true", DisplayName = "Delete Friends")) - static UNakamaClientRemoveFriends* RemoveFriends(UNakamaClient* Client, UNakamaSession *Session, TArray Ids, TArray Usernames); - - virtual void Activate() override; - -private: - TArray Ids; - TArray Usernames; -}; - -/** - * Block Friends - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientBlockFriends : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Block one or more friends by id. - * - * @param Ids The ids of the users to block. - * @param Usernames The usernames of the users to block. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Friends", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientBlockFriends* BlockFriends(UNakamaClient* Client, UNakamaSession *Session, TArray Ids, TArray Usernames); - - virtual void Activate() override; - -private: - TArray Ids; - TArray Usernames; -}; - - -// --- Groups --- // - -/** - * Create Group - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCreateGroup, FNakamaError, Error, FNakamaGroup, Group); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientCreateGroup : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FCreateGroup OnSuccess; - - UPROPERTY(BlueprintAssignable) - FCreateGroup OnError; - - /** - * Create a group. - * - * @param GroupName The name for the group. - * @param Description A description for the group. - * @param AvatarUrl An avatar url for the group. - * @param LanguageTag A language tag in BCP-47 format for the group. - * @param Open True if the group should have open membership. - * @param MaxMembers Maximum number of group members. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientCreateGroup* CreateGroup(UNakamaClient* Client, UNakamaSession *Session, FString GroupName, FString Description, FString AvatarUrl, FString LanguageTag, bool Open, int32 MaxMembers); - - virtual void Activate() override; - -private: - FString GroupName; - FString Description; - FString AvatarUrl; - FString LanguageTag; - bool Open; - int32 MaxMembers; -}; - - -/** - * List Groups - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListGroups, FNakamaError, Error, FNakamaGroupList, GroupList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListGroups : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListGroups OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListGroups OnError; - - /** - * List groups on the server. - * - * @param GroupNameFilter The name filter to apply to the group list. - * @param Limit The number of groups to list. - * @param Cursor A cursor for the current position in the groups to list. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListGroups* ListGroups(UNakamaClient* Client, UNakamaSession *Session, FString GroupNameFilter, int32 Limit, FString Cursor); - - virtual void Activate() override; - -private: - FString GroupNameFilter; - int32 Limit; - FString Cursor; - -}; - - -/** - * Join Group - */ - -//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListGroups, FNakamaError, Error, FNakamaGroupList, GroupList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientJoinGroup : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Join a group if it has open membership or request to join it. - * - * @param GroupId The id of the group to join. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientJoinGroup* JoinGroup(UNakamaClient* Client, UNakamaSession *Session, FString GroupId); - - virtual void Activate() override; - -private: - FString GroupId; - -}; - - -/** - * List Groups - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListUserGroups, FNakamaError, Error, FNakamaUserGroupList, UserGroupList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListUserGroups : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListUserGroups OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListUserGroups OnError; - - /** - * List of groups the current user is a member of. - * @param UserId The id of the user whose groups to list. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListUserGroups* ListUserGroups(UNakamaClient* Client, UNakamaSession *Session, FString UserId, int32 Limit, ENakamaGroupState State, FString Cursor); - - virtual void Activate() override; - -private: - FString UserId; - int32 Limit; - ENakamaGroupState State; - FString Cursor; - -}; - -/** - * List Group Users - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListListGroupUsers, FNakamaError, Error, FNakamaGroupUsersList, GroupUsersList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListListGroupUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListListGroupUsers OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListListGroupUsers OnError; - - /** - * List all users part of the group. - * - * @param GroupId The id of the group. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListListGroupUsers* ListGroupUsers(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, int32 Limit, ENakamaGroupState State, FString Cursor); - - virtual void Activate() override; - -private: - FString GroupId; - int32 Limit; - ENakamaGroupState State; - FString Cursor; - -}; - -/** - * Update Group - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientUpdateGroup : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Update a group. - * - * The user must have the correct access permissions for the group. - * - * @param GroupId The id of the group to update. - * @param Name A new name for the group. - * @param Description A new description for the group. - * @param AvatarUrl A new avatar url for the group. - * @param LanguageTag A new language tag in BCP-47 format for the group. - * @param Open True if the group should have open membership. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientUpdateGroup* UpdateGroup(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, FString Name, FString Description, FString AvatarUrl, FString LanguageTag, bool Open); - - virtual void Activate() override; - -private: - FString GroupId; - FString Name; - FString Description; - FString AvatarUrl; - FString LanguageTag; - bool Open; - -}; - - -/** - * Update Group - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientLeaveGroup : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Leave a group by id. - * - * @param GroupId The id of the group to leave. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientLeaveGroup* LeaveGroup(UNakamaClient* Client, UNakamaSession *Session, FString GroupId); - - virtual void Activate() override; - -private: - FString GroupId; - -}; - -/** - * Add Group Users - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientAddGroupUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Add one or more users to the group. - * - * @param GroupId The id of the group to add users into. - * @param UserIds The ids of the users to add or invite to the group. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientAddGroupUsers* AddGroupUsers(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, TArray UserIds); - - virtual void Activate() override; - -private: - FString GroupId; - TArray UserIds; - -}; - -/** - * Promote Group Users - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientPromoteGroupUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Promote a set of users in a group to the next role up. - * - * @param GroupId The group ID to promote in. - * @param UserIds The ids of the users to promote. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientPromoteGroupUsers* PromoteGroupUsers(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, TArray UserIds); - - virtual void Activate() override; - -private: - FString GroupId; - TArray UserIds; - -}; - - -/** - * Kick Group Users - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientKickGroupUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Kick one or more users from the group. - * - * @param GroupId The id of the group. - * @param UserIds The ids of the users to kick. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientKickGroupUsers* KickGroupUsers(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, TArray UserIds); - - virtual void Activate() override; - -private: - FString GroupId; - TArray UserIds; - -}; - -/** - * Kick Group Users - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientDemoteGroupUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - - /** - * Demote a set of users in a group to the next role down. - * - * @param GroupId The group ID to demote in. - * @param UserIds The ids of the users to demote. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientDemoteGroupUsers* DemoteGroupUsers(UNakamaClient* Client, UNakamaSession *Session, FString GroupId, TArray UserIds); - - virtual void Activate() override; - -private: - FString GroupId; - TArray UserIds; - -}; - -/** - * Delete Group - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientDeleteGroup : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - /** - * Delete a group by id. - * - * @param GroupId The group id to to remove. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Groups", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientDeleteGroup* DeleteGroup(UNakamaClient* Client, UNakamaSession *Session, FString GroupId); - - virtual void Activate() override; - -private: - FString GroupId; - -}; - - -// --- Notifications --- // - - -/** - * List Notifications - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FListNotifications, FNakamaError, Error, FNakamaNotificationList, NotificationList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListNotifications : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListNotifications OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListNotifications OnError; - - /** - * List notifications for the user with an optional cursor. - * - * @param Limit The number of notifications to list. - * @param Cursor A cursor for the current position in notifications to list. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Notifications", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListNotifications* ListNotifications(UNakamaClient* Client, UNakamaSession *Session, int32 Limit, FString Cursor); - - virtual void Activate() override; - -private: - int32 Limit; - FString Cursor; - -}; - - -/** - * Delete Notifications - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientDeleteNotifications : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Delete one or more notifications by id. - * - * @param NotificationIds The notification ids to remove. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Notifications", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientDeleteNotifications* DeleteNotifications(UNakamaClient* Client, UNakamaSession *Session, TArray NotificationIds); - - virtual void Activate() override; - -private: - TArray NotificationIds; - -}; - - -// --- Storage --- // - -/** - * Write Storage Objects - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWriteStorageObjects, FNakamaError, Error, FNakamaStoreObjectAcks, StorageObjectsAcks); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientWriteStorageObjects : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnWriteStorageObjects OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnWriteStorageObjects OnError; - - /** - * Write objects to the storage engine. - * - * @param StorageObjectsData The objects to write. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Storage", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientWriteStorageObjects* WriteStorageObjects(UNakamaClient* Client, UNakamaSession *Session, TArray StorageObjectsData); - - virtual void Activate() override; - -private: - TArray StorageObjectsData; -}; - -/** - * Read Storage Objects - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnReadStorageObjects, FNakamaError, Error, const FNakamaStorageObjectList&, StorageObjects); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientReadStorageObjects : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnReadStorageObjects OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnReadStorageObjects OnError; - - /** - * Read one or more objects from the storage engine. - * - * @param StorageObjectsData The objects to read. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Storage", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientReadStorageObjects* ReadStorageObjects(UNakamaClient* Client, UNakamaSession *Session, TArray StorageObjectsData); - - virtual void Activate() override; - -private: - TArray StorageObjectsData; -}; - -/** - * List Storage Objects - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListStorageObjects, FNakamaError, Error, FNakamaStorageObjectList, StorageObjectList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListtorageObjects : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListStorageObjects OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListStorageObjects OnError; - - /** - * List storage objects in a collection which belong to a specific user and have public read access. - * - * @param Collection The collection to list over. - * @param UserId The user ID of the user to list objects for. - * @param Limit The number of objects to list. - * @param Cursor A cursor to paginate over the collection. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Storage", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListtorageObjects* ListStorageObjects(UNakamaClient* Client, UNakamaSession *Session, FString Collection, FString UserId, int32 Limit, FString Cursor); - - virtual void Activate() override; - -private: - - FString Collection; - FString UserId; - int32 Limit; - FString Cursor; - -}; - - -/** - * Remove Storage Objects - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientRemoveStorageObjects : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Delete one or more storage objects. - * - * @param StorageObjectsData The ids of the objects to delete. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Storage", meta = (BlueprintInternalUseOnly = "true", DisplayName = "Delete Storage Objects")) - static UNakamaClientRemoveStorageObjects* RemoveStorageObjects(UNakamaClient* Client, UNakamaSession *Session, TArray StorageObjectsData); - - virtual void Activate() override; - -private: - - TArray StorageObjectsData; - -}; - - -// --- RPC --- // - -/** - * RPC - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRPCResponse, FNakamaError, Error, FNakamaRPC, RPCResponse); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientRPC : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnRPCResponse OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnRPCResponse OnError; - - /** - * Send an RPC message to the server. - * - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|RPC", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientRPC* RPC(UNakamaClient* Client, UNakamaSession *Session, FString FunctionId, FString Payload); - - virtual void Activate() override; - -private: - - FString FunctionId; - FString Payload; -}; - -/** - * RPC HttpKey - */ - -//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRPCResponse, FNakamaError, Error, FNakamaRPC, RPCResponse); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientRPCHttpKey : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY(BlueprintAssignable) - FOnRPCResponse OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnRPCResponse OnError; - - /** - * Send an RPC message to the server. - * - * @param Client The Client to use. - * @param HttpKey The HTTP key for the server. - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|RPC", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientRPCHttpKey* RPCHttpKey(UNakamaClient* Client, FString HttpKey, FString FunctionId, FString Payload); - - virtual void Activate() override; - -private: - - FString HttpKey; - FString FunctionId; - FString Payload; -}; - - -// --- Chat --- // - -/** - * List Channel Messages - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnChannelMessagesListed, FNakamaError, Error, FNakamaChannelMessageList, ChannelMessages); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListChannelMessages : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnChannelMessagesListed OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnChannelMessagesListed OnError; - - /** - * List messages from a chat channel. - * - * @param Client The Client to use. - * @param Session The session of the user. - * @param ChannelId A channel identifier. - * @param Limit The number of chat messages to list. - * @param Cursor A cursor for the current position in the messages history to list. - * @param Forward Fetch messages forward from the current cursor (or the start). - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListChannelMessages* ListChannelMessages(UNakamaClient* Client, UNakamaSession *Session, FString ChannelId, int32 Limit, FString Cursor, bool Forward); - - virtual void Activate() override; - -private: - - FString ChannelId; - int32 Limit; - FString Cursor; - bool Forward; -}; - - -// --- Leaderboards --- // - - -/** - * Write Leaderboard Record - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWriteLeaderboardRecord, FNakamaError, Error, FNakamaLeaderboardRecord, LeaderboardRecord); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientWriteLeaderboardRecord : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FWriteLeaderboardRecord OnSuccess; - - UPROPERTY(BlueprintAssignable) - FWriteLeaderboardRecord OnError; - - /** - * Write a record to a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to write. - * @param Score The score for the leaderboard record. - * @param SubScore The subscore for the leaderboard record. - * @param Metadata The metadata for the leaderboard record. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Leaderboards", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientWriteLeaderboardRecord* WriteLeaderboardRecord(UNakamaClient* Client, UNakamaSession *Session, FString LeaderboardId, int64 Score, int64 SubScore, FString Metadata); - - virtual void Activate() override; - -private: - - FString LeaderboardId; - int64 Score; - int64 SubScore; - FString Metadata; -}; - -/** - * List Leaderboard Records - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FListLeaderboardRecords, FNakamaError, Error, FNakamaLeaderboardRecordList, LeaderboardRecordList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListLeaderboardRecords : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListLeaderboardRecords OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListLeaderboardRecords OnError; - - /** - * List records from a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerIds Record owners to fetch with the list of records. - * @param Limit The number of records to list. - * @param Cursor A cursor for the current position in the leaderboard records to list. - * @param ListBy List by either Score or Friends - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Leaderboards", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListLeaderboardRecords* ListLeaderboardRecords(UNakamaClient* Client, UNakamaSession *Session, FString LeaderboardId, TArray OwnerIds, int32 Limit, FString Cursor, ENakamaLeaderboardListBy ListBy); - - virtual void Activate() override; - -private: - - FString LeaderboardId; - TArray OwnerIds; - int32 Limit; - FString Cursor; - ENakamaLeaderboardListBy ListBy; -}; - - -/** - * List Leaderboard Records Around Owner - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListLeaderboardRecordsAroundOwner : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListLeaderboardRecords OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListLeaderboardRecords OnError; - - /** - * List leaderboard records from a given leaderboard around the owner. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Leaderboards", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListLeaderboardRecordsAroundOwner* ListLeaderboardRecordsAroundOwner(UNakamaClient* Client, UNakamaSession *Session, FString LeaderboardId, FString OwnerId, int32 Limit); - - virtual void Activate() override; - -private: - - FString LeaderboardId; - FString OwnerId; - int32 Limit; -}; - -/** - * Delete Leaderboard Record - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientDeleteLeaderboardRecord : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnAnyError OnError; - - UPROPERTY(BlueprintAssignable) - FOnSuccessful OnSuccess; - - - /** - * Delete a leaderboard record. - * - * @param LeaderboardId The id of the leaderboard with the record to be deleted. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Leaderboards", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientDeleteLeaderboardRecord* DeleteLeaderboardRecord(UNakamaClient* Client, UNakamaSession *Session, FString LeaderboardId); - - virtual void Activate() override; - -private: - - FString LeaderboardId; - -}; - -// --- Tournaments --- // - -/** - * Write Tournament Record - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientWriteTournamentRecord : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FWriteLeaderboardRecord OnSuccess; - - UPROPERTY(BlueprintAssignable) - FWriteLeaderboardRecord OnError; - - /** - * A request to submit a score to a tournament. - * - * @param TournamentId The tournament ID to write the record for. - * @param Score The score value to submit. - * @param SubScore An optional secondary value. - * @param Metadata A JSON object of additional properties. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Tournaments", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientWriteTournamentRecord* WriteTournamentRecord(UNakamaClient* Client, UNakamaSession *Session, FString TournamentId, int64 Score, int64 SubScore, FString Metadata); - - virtual void Activate() override; - -private: - - FString TournamentId; - int64 Score; - int64 SubScore; - FString Metadata; -}; - - -/** - * List Tournaments Records - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FListTournamentRecords, FNakamaError, Error, FNakamaTournamentRecordList, TournamentRecordList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListTournamentRecords : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListTournamentRecords OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListTournamentRecords OnError; - - /** - * List tournament records from a given tournament. - * - * @param TournamentId The ID of the tournament to list for. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next or previous page cursor. - * @param OwnerIds One or more owners to retrieve records for. - * @param ListBy List By Score or Friends - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Tournaments", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListTournamentRecords* ListTournamentRecords(UNakamaClient* Client, UNakamaSession *Session, FString TournamentId, TArray OwnerIds, int32 Limit, FString Cursor, ENakamaLeaderboardListBy ListBy); - - virtual void Activate() override; - -private: - - FString TournamentId; - TArray OwnerIds; - int32 Limit; - FString Cursor; - ENakamaLeaderboardListBy ListBy; -}; - - -/** - * List Tournament Records Around Owner - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListTournamentRecordsAroundOwner : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListTournamentRecords OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListTournamentRecords OnError; - - /** - * List tournament records from a given tournament around the owner. - * - * @param TournamentId The ID of the tournament to list for. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Tournaments", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListTournamentRecordsAroundOwner* ListTournamentRecordsAroundOwner(UNakamaClient* Client, UNakamaSession *Session, FString TournamentId, FString OwnerId, int32 Limit); - - virtual void Activate() override; - -private: - - FString TournamentId; - FString OwnerId; - int32 Limit; -}; - -/** - * Join Tournament - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnJoinTournament, FNakamaError, Error, FString, TournamentId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientJoinTournament : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnJoinTournament OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnJoinTournament OnError; - - /** - * Join a tournament if it has open membership or request to join it. - * - * @param TournamentId The id of the tournament to join. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Tournaments", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientJoinTournament* JoinTournament(UNakamaClient* Client, UNakamaSession *Session, FString TournamentId); - - virtual void Activate() override; - -private: - - FString TournamentId; -}; - - -/** - * List Tournaments - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FListTournaments, FNakamaError, Error, FNakamaTournamentList, TournamentList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListTournaments : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FListTournaments OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListTournaments OnError; - - /** - * List active/upcoming tournaments based on given filters. - * - * @param CategoryStart The start of the categories to include. Defaults to 0. - * @param CategoryEnd The end of the categories to include. Defaults to 128. - * @param StartTime The start time for tournaments. Defaults to current Unix time. - * @param EndTime The end time for tournaments. Defaults to +1 year from current Unix time. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next page cursor for listings. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Tournaments", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListTournaments* ListTournaments(UNakamaClient* Client, UNakamaSession *Session, int32 CategoryStart, int32 CategoryEnd, int32 StartTime, int32 EndTime, int32 Limit, FString Cursor); - - virtual void Activate() override; - -private: - int32 CategoryStart; - int32 CategoryEnd; - int32 StartTime; - int32 EndTime; - int32 Limit; - FString Cursor; - -}; - - -/** - * List Parties - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListParties, FNakamaError, Error, FNakamaPartyList, PartyList); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaClientListParties : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr NakamaClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnListParties OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnListParties OnError; - - /** - * List parties and optionally filter by matching criteria. - * - * @param Limit Limit the number of returned parties. - * @param Open Optionally filter by open/closed parties. - * @param Query Arbitrary label query. - * @param Cursor Cursor for the next page of results, if any. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaClientListParties* ListParties( - UNakamaClient* Client, - UNakamaSession *Session, - int32 Limit, - bool Open, - FString Query, - FString Cursor - ); - - virtual void Activate() override; - -private: - - int32 Limit; - bool Open; - FString Query; - FString Cursor; - -}; diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaLibrary.h b/Nakama/Source/NakamaBlueprints/Public/NakamaLibrary.h deleted file mode 100644 index 01b4da4d8..000000000 --- a/Nakama/Source/NakamaBlueprints/Public/NakamaLibrary.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaClient.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "NakamaLibrary.generated.h" - - -/** - * - */ -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -public: - - - UFUNCTION(BlueprintCallable, Category = "Nakama|Utilities") - static FNakamaChatMessage ChatMessageJsonToStruct(FString JsonMessage); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Utilities") - static FString ChatMessageStructToJson(FNakamaChatMessage MessageStruct); - - -}; diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaRealtimeClientRequests.h b/Nakama/Source/NakamaBlueprints/Public/NakamaRealtimeClientRequests.h deleted file mode 100644 index 8484b3f53..000000000 --- a/Nakama/Source/NakamaBlueprints/Public/NakamaRealtimeClientRequests.h +++ /dev/null @@ -1,1208 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRealtimeClient.h" -#include "Kismet/BlueprintAsyncActionBase.h" -#include "NakamaStatus.h" -#include "NakamaRtError.h" -#include "NakamaChannelTypes.h" -#include "NakamaMatch.h" -#include "NakamaMatchTypes.h" -#include "NakamaParty.h" -#include "NakamaPresence.h" -#include "NakamaChat.h" -#include "NakamaRealtimeClientRequests.generated.h" - -/** - * - */ -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRequests : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -}; - - -// --- Messaging --- // - -// Global Delegates for empty success / error -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnyRtError, FNakamaRtError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRealtimeSuccessful); - -/** - * Connect to Server. - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRealtimeClientConnectError, FNakamaRtError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRealtimeClientConnect); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientConnect : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnRealtimeClientConnectError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeClientConnect OnSuccess; - - /** - * Connect to the Server. - * - * @param RealtimeClient The Realtime Client (Socket) to use. - * @param Session The Session to use. - * @param bInShowAsOnline Show as online. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Setup", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientConnect* Connect(UNakamaRealtimeClient* RealtimeClient, UNakamaSession* Session, bool bInShowAsOnline); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - UPROPERTY() - TObjectPtr UserSession; - - bool bShowAsOnline; - -}; - - -/** - * Send Chat Message - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWriteChannelMessage, FNakamaRtError, Error, FNakamaChannelMessageAck, MessageAck); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientSendMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnWriteChannelMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnWriteChannelMessage OnError; - - /** - * Send a chat message to a channel on the server. - * - * @param ChannelId The channel to send on. - * @param Content The content of the chat message. Must be a JSON object. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat|Messaging", meta = (BlueprintInternalUseOnly = "true", DisplayName = "Write Chat Message")) - static UNakamaRealtimeClientSendMessage* SendMessage(UNakamaRealtimeClient* RealtimeClient, FString ChannelId, FString Content); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString ChannelId; - FString Content; - -}; - - -/** - * Send Direct Chat Message - */ - - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientSendDirectMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnWriteChannelMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnWriteChannelMessage OnError; - - /** - * Send a direct chat message to another user. - * - * @param UserID The user to send to. - * @param Content The content of the chat message. Must be a JSON object. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat|Messaging", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientSendDirectMessage* SendDirectMessage(UNakamaRealtimeClient* RealtimeClient, FString UserID, FString Content); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString UserID; - FString Content; - -}; - -/** - * Update Chat Message - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUpdateChatMessage, FNakamaRtError, Error, FNakamaChannelMessageAck, MessageAck); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientUpdateChatMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnUpdateChatMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnUpdateChatMessage OnError; - - /** - * Update a chat message to a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to update. - * @param Content The content update for the message. Must be a JSON object. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat|Messaging", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientUpdateChatMessage* UpdateChatMessage(UNakamaRealtimeClient* RealtimeClient, FString ChannelId, FString MessageId, FString Content); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString ChannelId; - FString Content; - FString MessageId; - -}; - -/** - * Remove Chat Message - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRemoveChatMessage, FNakamaRtError, Error, FNakamaChannelMessageAck, MessageAck); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRemoveChatMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnRemoveChatMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnRemoveChatMessage OnError; - - /** - * Remove a chat message from a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to remove. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat|Messaging", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientRemoveChatMessage* RemoveChatMessage(UNakamaRealtimeClient* RealtimeClient, FString ChannelId, FString MessageId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString ChannelId; - FString MessageId; -}; - -// --- Chat --- // - - -/** - * Join Chat - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FJoinChat, FNakamaRtError, Error, FNakamaChannel, Channel); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientJoinChat : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FJoinChat OnSuccess; - - UPROPERTY(BlueprintAssignable) - FJoinChat OnError; - - /** - * Join a chat channel on the server. - * - * @param ChatId The target channel to join. - * @param ChannelType The type of channel to join. - * @param Persistence True if chat messages should be stored. - * @param Hidden True if the user should be hidden on the channel. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientJoinChat* JoinChat(UNakamaRealtimeClient* RealtimeClient, FString ChatId, ENakamaChannelType ChannelType, bool Persistence, bool Hidden); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString ChatId; - ENakamaChannelType ChannelType; - bool Persistence; - bool Hidden; -}; - - - -/** - * Leave Chat - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FLeaveChat, FNakamaRtError, Error, FString, ChannelId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientLeaveChat : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FLeaveChat OnSuccess; - - UPROPERTY(BlueprintAssignable) - FLeaveChat OnError; - - /** - * Leave a chat channel on the server. - * - * @param ChannelId The channel to leave. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Chat", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientLeaveChat* LeaveChat(UNakamaRealtimeClient* RealtimeClient, FString ChannelId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString ChannelId; -}; - - -// --- Matchmaker --- // - -/** - * Join Matchmaker - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAddMatchmaker, FNakamaRtError, Error, FNakamaMatchmakerTicket, Ticket); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientAddMatchmaker : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FAddMatchmaker OnSuccess; - - UPROPERTY(BlueprintAssignable) - FAddMatchmaker OnError; - - /** - * Join the matchmaker pool and search for opponents on the server. - * - * @param MinCount The minimum number of players to compete against. - * @param MaxCount The maximum number of players to compete against. - * @param Query A matchmaker query to search for opponents. - * @param StringProperties A set of k/v properties to provide in searches. - * @param NumericProperties A set of k/v numeric properties to provide in searches. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - * @param IgnoreCountMultiple Ignore Countmultiple parameter. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Matchmaker", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientAddMatchmaker* AddMatchmaker(UNakamaRealtimeClient* RealtimeClient, int32 MinCount, int32 MaxCount, FString Query, TMap StringProperties, TMap NumericProperties, int32 CountMultiple, bool IgnoreCountMultiple); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - int32 MinCount; - int32 MaxCount; - FString Query; - TMap StringProperties; - TMap NumericProperties; - int32 CountMultiple; - bool IgnoreCountMultiple; - - -}; - - -/** - * Leave Matchmaker - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FLeaveMatchmaker, FNakamaRtError, Error, FString, Ticket); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientLeaveMatchmaker : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FLeaveMatchmaker OnSuccess; - - UPROPERTY(BlueprintAssignable) - FLeaveMatchmaker OnError; - - /** - * Leave the matchmaker pool by ticket. - * - * @param Ticket The ticket returned by the matchmaker on join. See NMatchmakerTicket.ticket. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Matchmaker", meta = (BlueprintInternalUseOnly = "true", DisplayName = "Remove Matchmaker")) - static UNakamaRealtimeClientLeaveMatchmaker* LeaveMatchmaker(UNakamaRealtimeClient* RealtimeClient, FString Ticket); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString Ticket; - -}; - - -// --- Statuses --- // - - -/** - * Update Status - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientUpdateStatus : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - /** - * Update the user's status online. - * - * @param StatusMessage The new status of the user. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Status", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientUpdateStatus* UpdateStatus(UNakamaRealtimeClient* RealtimeClient, FString StatusMessage); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString StatusMessage; - -}; - - -/** - * Set Appear Offline - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientSetAppearOffline : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - /** - * Update the user's status to offline, appearing invisible to others. - * - *@param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Status", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientSetAppearOffline* SetAppearOffline(UNakamaRealtimeClient* RealtimeClient); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString StatusMessage; - -}; - - -/** - * Follow Users - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFollowUsers, FNakamaRtError, Error, FNakamaStatus, Status); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientFollowUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FFollowUsers OnSuccess; - - UPROPERTY(BlueprintAssignable) - FFollowUsers OnError; - - /** - * Follow one or more users for status updates. - * - * @param UserIds The user Ids to follow. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Status", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientFollowUsers* FollowUsers(UNakamaRealtimeClient* RealtimeClient, TArray UserIds); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - TArray UserIds; - -}; - - -/** - * UnFollow Users - */ - -//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFollowUsers, FNakamaRtError, Error, FNakamaStatus, Status); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientUnFollowUsers : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - - /** - * Unfollow status updates for one or more users. - * - * @param UserIds The ids of users to unfollow. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Status", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientUnFollowUsers* UnFollowUsers(UNakamaRealtimeClient* RealtimeClient, TArray UserIds); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - TArray UserIds; - -}; - -// --- Realtime and Match (To send RPC, please use normal Client) --- // - - -/** - * Create Match - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCreateMatch, FNakamaRtError, Error, FNakamaMatch, Match); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientCreateMatch : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FCreateMatch OnSuccess; - - UPROPERTY(BlueprintAssignable) - FCreateMatch OnError; - - /** - * Create a multiplayer match on the server. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Match", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientCreateMatch* CreateMatch(UNakamaRealtimeClient* RealtimeClient); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - -}; - - -/** - * Join Match - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FJoinMatch, FNakamaRtError, Error, FNakamaMatch, Match); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientJoinMatch : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FJoinMatch OnSuccess; - - UPROPERTY(BlueprintAssignable) - FJoinMatch OnError; - - /** - * Join a multiplayer match by ID. - * - * @param MatchId A match ID. - * @param MetaData Metadata. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Match", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientJoinMatch* JoinMatch(UNakamaRealtimeClient* RealtimeClient, FString MatchId, TMap MetaData); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString MatchId; - TMap MetaData; -}; - - -/** - * Join Match by Token - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientJoinMatchByToken : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FJoinMatch OnSuccess; - - UPROPERTY(BlueprintAssignable) - FJoinMatch OnError; - - /** - * Join a multiplayer match with a matchmaker. - * - * @param Token A matchmaker ticket result object. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Match", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientJoinMatchByToken* JoinMatchByToken(UNakamaRealtimeClient* RealtimeClient, FString Token); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString Token; -}; - - -/** - * Leave Match - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FLeaveMatch, FNakamaRtError, Error, FString, MatchId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientLeaveMatch : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FLeaveMatch OnSuccess; - - UPROPERTY(BlueprintAssignable) - FLeaveMatch OnError; - - /** - * Leave a match on the server. - * - * @param MatchId The match to leave. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Match", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientLeaveMatch* LeaveMatch(UNakamaRealtimeClient* RealtimeClient, FString MatchId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString MatchId; -}; - - -// --- Parties --- // - -/** - * Create Party - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCreateParty, FNakamaRtError, Error, FNakamaParty, Party); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientCreateParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FCreateParty OnSuccess; - - UPROPERTY(BlueprintAssignable) - FCreateParty OnError; - - /** - * Create a party. - * - * @param Open Whether or not the party will require join requests to be approved by the party leader. - * @param MaxSize Maximum number of party members. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientCreateParty* CreateParty(UNakamaRealtimeClient* RealtimeClient, bool Open, int32 MaxSize); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - bool Open; - int32 MaxSize; -}; - - -/** - * Join Party - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FJoinParty, FNakamaRtError, Error, FString, PartyId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientJoinParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FJoinParty OnSuccess; - - UPROPERTY(BlueprintAssignable) - FJoinParty OnError; - - /** - * Join a party. - * - * @param PartyId Party ID. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientJoinParty* JoinParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - -}; - - -/** - * Leave Party - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FLeaveParty, FNakamaRtError, Error, FString, PartyId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientLeaveParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FLeaveParty OnSuccess; - - UPROPERTY(BlueprintAssignable) - FLeaveParty OnError; - - /** - * Leave the party. - * - * @param PartyId Party ID. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientLeaveParty* LeaveParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - -}; - - - -/** - * List Party Join Requests - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FListPartyJoinRequests, FNakamaRtError, Error, FNakamaPartyJoinRequest, PartyId); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientListPartyJoinRequests : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FListPartyJoinRequests OnSuccess; - - UPROPERTY(BlueprintAssignable) - FListPartyJoinRequests OnError; - - /** - * Request a list of pending join requests for a party. - * - * @param PartyId Party ID. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientListPartyJoinRequests* ListPartyJoinRequests(UNakamaRealtimeClient* RealtimeClient, FString PartyId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - -}; - -/** - * Promote Party Member - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPromotePartyMember : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - /** - * Promote a new party leader. - * @param PartyId Party ID. - * @param PartyMember The presence of an existing party member to promote as the new leader. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientPromotePartyMember* PromotePartyMember(UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence PartyMember); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - FNakamaUserPresence PartyMember; - -}; - - -/** - * Remove Matchmaker Party - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRemoveMatchmakerParty, FNakamaRtError, Error, FString, Ticket); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRemoveMatchmakerParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FRemoveMatchmakerParty OnSuccess; - - UPROPERTY(BlueprintAssignable) - FRemoveMatchmakerParty OnError; - - /** - * Cancel a party matchmaking process using a ticket. - * @param PartyId Party ID. - * @param Ticket The ticket to cancel. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientRemoveMatchmakerParty* RemoveMatchmakerParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId, FString Ticket); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - FString Ticket; - -}; - -/** - * Remove Party Member - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRemovePartyMember : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - - /** - * Kick a party member, or decline a request to join. - * @param PartyId Party ID to remove/reject from. - * @param Presence The presence to remove or reject. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientRemovePartyMember* RemovePartyMember(UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence Presence); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - FNakamaUserPresence Presence; - -}; - - -/** - * Accept Party Member - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientAcceptPartyMember : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - - /** - * Accept a party member's request to join the party. - * - * @param PartyId The party ID to accept the join request for. - * @param Presence The presence to accept as a party member. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientAcceptPartyMember* AcceptPartyMember(UNakamaRealtimeClient* RealtimeClient, FString PartyId, FNakamaUserPresence Presence); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - FNakamaUserPresence Presence; - -}; - - -/** - * Add Matchmaker Party - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAddMatchmakerParty, FNakamaRtError, Error, FNakamaPartyMatchmakerTicket, Ticket); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientAddMatchmakerParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FAddMatchmakerParty OnSuccess; - - UPROPERTY(BlueprintAssignable) - FAddMatchmakerParty OnError; - - /** - * Begin matchmaking as a party. - * - * @param PartyId Party ID. - * @param Query Filter query used to identify suitable users. - * @param MinCount Minimum total user count to match together. - * @param MaxCount Maximum total user count to match together. - * @param StringProperties String properties. - * @param NumericProperties Numeric properties. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Matchmaker", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientAddMatchmakerParty* AddMatchmakerParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId, int32 MinCount, int32 MaxCount, FString Query, TMap StringProperties, TMap NumericProperties, int32 CountMultiple, bool IgnoreCountMultiple); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - int32 MinCount; - int32 MaxCount; - FString Query; - TMap StringProperties; - TMap NumericProperties; - int32 CountMultiple; - bool IgnoreCountMultiple; - - -}; - -/** - * Close Party - */ - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientCloseParty : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY(BlueprintAssignable) - FOnAnyRtError OnError; - - UPROPERTY(BlueprintAssignable) - FOnRealtimeSuccessful OnSuccess; - - /** - * End a party, kicking all party members and closing it. - * @param PartyId The ID of the party. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties", meta = (BlueprintInternalUseOnly = "true")) - static UNakamaRealtimeClientCloseParty* CloseParty(UNakamaRealtimeClient* RealtimeClient, FString PartyId); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString PartyId; - -}; - - -/** - * RPC - */ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRtRPCResponse, FNakamaRtError, Error, FNakamaRPC, RPCResponse); - -UCLASS() -class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRPC : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - - UPROPERTY(BlueprintAssignable) - FOnRtRPCResponse OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnRtRPCResponse OnError; - - /** - * Send an RPC message to the server. - * - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param RealtimeClient The Realtime Client (Socket) to use. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|RPC", meta = (BlueprintInternalUseOnly = "true", DisplayName="RPC Realtime")) - static UNakamaRealtimeClientRPC* RPC(UNakamaRealtimeClient* RealtimeClient, const FString& FunctionId, const FString& Payload); - - virtual void Activate() override; - -private: - - UPROPERTY() - TObjectPtr RealtimeClient; - - FString FunctionId; - FString Payload; -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaRtClientBlueprintLibrary.h b/Nakama/Source/NakamaBlueprints/Public/NakamaRtClientBlueprintLibrary.h new file mode 100644 index 000000000..87fe0e41e --- /dev/null +++ b/Nakama/Source/NakamaBlueprints/Public/NakamaRtClientBlueprintLibrary.h @@ -0,0 +1,1697 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "NakamaRtTypes.h" +#include "NakamaWebSocketSubsystem.h" + +#include "NakamaRtClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaRtError, const FNakamaRtError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNakamaRtSuccess); + +// ============================================================================ +// Async Action Classes (one per RT operation) +// ============================================================================ + +/** + * A realtime chat channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannel : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Channel"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannel* Channel( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Id, + const TArray& Presences, + FNakamaRtUserPresence Self_, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredId; + TArray StoredPresences; + FNakamaRtUserPresence StoredSelf; + FString StoredRoomName; + FString StoredGroupId; + FString StoredUserIdOne; + FString StoredUserIdTwo; +}; + +/** + * Join operation for a realtime chat channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelJoin : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelJoin"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelJoin* ChannelJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Target, + int32 Type, + bool Persistence, + bool Hidden); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredTarget; + int32 StoredType = 0; + bool StoredPersistence = false; + bool StoredHidden = false; +}; + +/** + * Leave a realtime channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelLeave : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelLeave"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelLeave* ChannelLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; +}; + +/** + * A message sent on a channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelMessage : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelMessage"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelMessage* ChannelMessage( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + int32 Code, + FString SenderId, + FString Username, + FString Content, + FString CreateTime, + FString UpdateTime, + bool Persistent, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + FString StoredMessageId; + int32 StoredCode = 0; + FString StoredSenderId; + FString StoredUsername; + FString StoredContent; + FString StoredCreateTime; + FString StoredUpdateTime; + bool StoredPersistent = false; + FString StoredRoomName; + FString StoredGroupId; + FString StoredUserIdOne; + FString StoredUserIdTwo; +}; + +/** + * A receipt reply from a channel message send operation. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelMessageAck : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelMessageAck"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelMessageAck* ChannelMessageAck( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + int32 Code, + FString Username, + FString CreateTime, + FString UpdateTime, + bool Persistent, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + FString StoredMessageId; + int32 StoredCode = 0; + FString StoredUsername; + FString StoredCreateTime; + FString StoredUpdateTime; + bool StoredPersistent = false; + FString StoredRoomName; + FString StoredGroupId; + FString StoredUserIdOne; + FString StoredUserIdTwo; +}; + +/** + * Send a message to a realtime channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelMessageSend : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelMessageSend"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelMessageSend* ChannelMessageSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString Content); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + FString StoredContent; +}; + +/** + * Update a message previously sent to a realtime channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelMessageUpdate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelMessageUpdate"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelMessageUpdate* ChannelMessageUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId, + FString Content); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + FString StoredMessageId; + FString StoredContent; +}; + +/** + * Remove a message previously sent to a realtime channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelMessageRemove : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelMessageRemove"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelMessageRemove* ChannelMessageRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + FString MessageId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + FString StoredMessageId; +}; + +/** + * A set of joins and leaves on a particular channel. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientChannelPresenceEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ChannelPresenceEvent"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientChannelPresenceEvent* ChannelPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString ChannelId, + const TArray& Joins, + const TArray& Leaves, + FString RoomName, + FString GroupId, + FString UserIdOne, + FString UserIdTwo); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredChannelId; + TArray StoredJoins; + TArray StoredLeaves; + FString StoredRoomName; + FString StoredGroupId; + FString StoredUserIdOne; + FString StoredUserIdTwo; +}; + +/** + * A logical error which may occur on the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientError : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Error"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientError* Error( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + int32 Code, + FString Message, + const TMap& Context); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + int32 StoredCode = 0; + FString StoredMessage; + TMap StoredContext; +}; + +/** + * A realtime match. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatch : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Match"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatch* Match( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + bool Authoritative, + FString Label, + int32 Size, + const TArray& Presences, + FNakamaRtUserPresence Self_); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredMatchId; + bool StoredAuthoritative = false; + FString StoredLabel; + int32 StoredSize = 0; + TArray StoredPresences; + FNakamaRtUserPresence StoredSelf; +}; + +/** + * Create a new realtime match. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchCreate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchCreate"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchCreate* MatchCreate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Name); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredName; +}; + +/** + * Realtime match data received from the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchData : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchData"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchData* MatchData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + FNakamaRtUserPresence Presence, + int64 OpCode, + const TArray& Data, + bool Reliable); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredMatchId; + FNakamaRtUserPresence StoredPresence; + int64 StoredOpCode = 0; + TArray StoredData; + bool StoredReliable = false; +}; + +/** + * Send realtime match data to the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchDataSend : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchDataSend"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchDataSend* MatchDataSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + int64 OpCode, + const TArray& Data, + const TArray& Presences, + bool Reliable); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredMatchId; + int64 StoredOpCode = 0; + TArray StoredData; + TArray StoredPresences; + bool StoredReliable = false; +}; + +/** + * Join an existing realtime match. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchJoin : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchJoin"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchJoin* MatchJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TMap& Metadata); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TMap StoredMetadata; +}; + +/** + * Leave a realtime match. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchLeave : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchLeave"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchLeave* MatchLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredMatchId; +}; + +/** + * A set of joins and leaves on a particular realtime match. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchPresenceEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchPresenceEvent"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchPresenceEvent* MatchPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString MatchId, + const TArray& Joins, + const TArray& Leaves); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredMatchId; + TArray StoredJoins; + TArray StoredLeaves; +}; + +/** + * Start a new matchmaking process. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchmakerAdd : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchmakerAdd"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchmakerAdd* MatchmakerAdd( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + int32 MinCount, + int32 MaxCount, + FString Query, + int32 CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + int32 StoredMinCount = 0; + int32 StoredMaxCount = 0; + FString StoredQuery; + int32 StoredCountMultiple = 0; + TMap StoredStringProperties; + TMap StoredNumericProperties; +}; + +/** + * A successful matchmaking result. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchmakerMatched : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchmakerMatched"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchmakerMatched* MatchmakerMatched( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket, + const TArray& Users, + FNakamaRtMatchmakerMatched_MatchmakerUser Self_); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredTicket; + TArray StoredUsers; + FNakamaRtMatchmakerMatched_MatchmakerUser StoredSelf; +}; + +/** + * Cancel an existing ongoing matchmaking process. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchmakerRemove : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchmakerRemove"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchmakerRemove* MatchmakerRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredTicket; +}; + +/** + * A ticket representing a new matchmaking process. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientMatchmakerTicket : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "MatchmakerTicket"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientMatchmakerTicket* MatchmakerTicket( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Ticket); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredTicket; +}; + +/** + * A collection of zero or more notifications. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientNotifications : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Notifications"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientNotifications* Notifications( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Notifications); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TArray StoredNotifications; +}; + +/** + * Execute an Lua function on the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientRpc : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Rpc"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientRpc* Rpc( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Id, + FString Payload, + FString HttpKey); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredId; + FString StoredPayload; + FString StoredHttpKey; +}; + +/** + * A snapshot of statuses for some set of users. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStatus : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Status"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStatus* Status( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Presences); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TArray StoredPresences; +}; + +/** + * Start receiving status updates for some set of users. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStatusFollow : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StatusFollow"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStatusFollow* StatusFollow( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& UserIds, + const TArray& Usernames); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TArray StoredUserIds; + TArray StoredUsernames; +}; + +/** + * A batch of status updates for a given user. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStatusPresenceEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StatusPresenceEvent"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStatusPresenceEvent* StatusPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& Joins, + const TArray& Leaves); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TArray StoredJoins; + TArray StoredLeaves; +}; + +/** + * Stop receiving status updates for some set of users. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStatusUnfollow : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StatusUnfollow"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStatusUnfollow* StatusUnfollow( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + const TArray& UserIds); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + TArray StoredUserIds; +}; + +/** + * Set the user's own status. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStatusUpdate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StatusUpdate"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStatusUpdate* StatusUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString Status); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredStatus; +}; + +/** + * A data message delivered over a stream. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStreamData : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StreamData"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStreamData* StreamData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FNakamaRtStream Stream, + FNakamaRtUserPresence Sender, + FString Data, + bool Reliable); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FNakamaRtStream StoredStream; + FNakamaRtUserPresence StoredSender; + FString StoredData; + bool StoredReliable = false; +}; + +/** + * A set of joins and leaves on a particular stream. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientStreamPresenceEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "StreamPresenceEvent"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientStreamPresenceEvent* StreamPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FNakamaRtStream Stream, + const TArray& Joins, + const TArray& Leaves); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FNakamaRtStream StoredStream; + TArray StoredJoins; + TArray StoredLeaves; +}; + +/** + * Application-level heartbeat and connection check. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPing : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Ping"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPing* Ping( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; +}; + +/** + * Application-level heartbeat and connection check response. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPong : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Pong"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPong* Pong( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; +}; + +/** + * Incoming information about a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientParty : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Party"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientParty* Party( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + bool Open, + bool Hidden, + int32 MaxSize, + FNakamaRtUserPresence Self_, + FNakamaRtUserPresence Leader, + const TArray& Presences, + FString Label); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + bool StoredOpen = false; + bool StoredHidden = false; + int32 StoredMaxSize = 0; + FNakamaRtUserPresence StoredSelf; + FNakamaRtUserPresence StoredLeader; + TArray StoredPresences; + FString StoredLabel; +}; + +/** + * Create a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyCreate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyCreate"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyCreate* PartyCreate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + bool Open, + int32 MaxSize, + FString Label, + bool Hidden); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + bool StoredOpen = false; + int32 StoredMaxSize = 0; + FString StoredLabel; + bool StoredHidden = false; +}; + +/** + * Join a party, or request to join if the party is not open. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyJoin : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyJoin"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyJoin* PartyJoin( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; +}; + +/** + * Leave a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyLeave : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyLeave"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyLeave* PartyLeave( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; +}; + +/** + * Promote a new party leader. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyPromote : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyPromote"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyPromote* PartyPromote( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FNakamaRtUserPresence StoredPresence; +}; + +/** + * Announcement of a new party leader. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyLeader : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyLeader"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyLeader* PartyLeader( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FNakamaRtUserPresence StoredPresence; +}; + +/** + * Accept a request to join. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyAccept : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyAccept"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyAccept* PartyAccept( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FNakamaRtUserPresence StoredPresence; +}; + +/** + * Kick a party member, or decline a request to join. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyRemove : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyRemove"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyRemove* PartyRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FNakamaRtUserPresence StoredPresence; +}; + +/** + * End a party, kicking all party members and closing it. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyClose : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyClose"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyClose* PartyClose( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; +}; + +/** + * Request a list of pending join requests for a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyJoinRequestList : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyJoinRequestList"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyJoinRequestList* PartyJoinRequestList( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; +}; + +/** + * Incoming notification for one or more new presences attempting to join the party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyJoinRequest : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyJoinRequest"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyJoinRequest* PartyJoinRequest( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + const TArray& Presences); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + TArray StoredPresences; +}; + +/** + * Begin matchmaking as a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyMatchmakerAdd : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyMatchmakerAdd"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyMatchmakerAdd* PartyMatchmakerAdd( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + int32 MinCount, + int32 MaxCount, + FString Query, + int32 CountMultiple, + const TMap& StringProperties, + const TMap& NumericProperties); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + int32 StoredMinCount = 0; + int32 StoredMaxCount = 0; + FString StoredQuery; + int32 StoredCountMultiple = 0; + TMap StoredStringProperties; + TMap StoredNumericProperties; +}; + +/** + * Cancel a party matchmaking process using a ticket. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyMatchmakerRemove : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyMatchmakerRemove"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyMatchmakerRemove* PartyMatchmakerRemove( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Ticket); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FString StoredTicket; +}; + +/** + * A response from starting a new party matchmaking process. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyMatchmakerTicket : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyMatchmakerTicket"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyMatchmakerTicket* PartyMatchmakerTicket( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Ticket); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FString StoredTicket; +}; + +/** + * Incoming party data delivered from the server. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyData : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyData"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyData* PartyData( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FNakamaRtUserPresence Presence, + int64 OpCode, + const TArray& Data); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FNakamaRtUserPresence StoredPresence; + int64 StoredOpCode = 0; + TArray StoredData; +}; + +/** + * Send data to a party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyDataSend : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyDataSend"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyDataSend* PartyDataSend( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + int64 OpCode, + const TArray& Data); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + int64 StoredOpCode = 0; + TArray StoredData; +}; + +/** + * Presence update for a particular party. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyPresenceEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyPresenceEvent"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyPresenceEvent* PartyPresenceEvent( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + const TArray& Joins, + const TArray& Leaves); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + TArray StoredJoins; + TArray StoredLeaves; +}; + +/** + * Update a party label. + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClientPartyUpdate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "PartyUpdate"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClientPartyUpdate* PartyUpdate( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem, + FString PartyId, + FString Label, + bool Open, + bool Hidden); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; + FString StoredPartyId; + FString StoredLabel; + bool StoredOpen = false; + bool StoredHidden = false; +}; diff --git a/Nakama/Source/NakamaCore/NakamaCore.build.cs b/Nakama/Source/NakamaCore/NakamaCore.build.cs deleted file mode 100644 index c591d4d74..000000000 --- a/Nakama/Source/NakamaCore/NakamaCore.build.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using UnrealBuildTool; - -public class NakamaCore : ModuleRules -{ - public NakamaCore(ReadOnlyTargetRules target) : base(target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - // Platform -> (buildLib, runtimeLib) - var libs = new Dictionary>() - { - {UnrealTargetPlatform.Linux, Tuple.Create(Path.Combine("linux-x64", "libnakama-sdk.so"), Path.Combine("linux-x64", "libnakama-sdk.so"))}, - }; - - if (target.Platform == UnrealTargetPlatform.Win64) - { - string configurationDirectory = null; - string arch = null; - - if (IsX64Arch()) - { - arch = "win-x64"; - } - else - { - arch = "win-arm64"; - } - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - configurationDirectory = "Debug"; - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", "nakama-sdk.pdb"), Path.Combine(ModuleDirectory, "libnakama", arch, configurationDirectory, "nakama-sdk.pdb")); - } - else - { - configurationDirectory = "Release"; - } - - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakama", arch, configurationDirectory, "nakama-sdk.lib")); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", "nakama-sdk.dll"), Path.Combine(ModuleDirectory, "libnakama", arch, configurationDirectory, "nakama-sdk.dll")); - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string dylibPath; - if (IsX64Arch()) - { - dylibPath = Path.Combine(ModuleDirectory, "libnakama", "macosx-x64", "libnakama-sdk.dylib"); - } - else - { - dylibPath = Path.Combine(ModuleDirectory, "libnakama", "macosx-arm64", "libnakama-sdk.dylib"); - } - - PublicDelayLoadDLLs.Add(dylibPath); - RuntimeDependencies.Add(dylibPath); - } - else if (Target.Platform == UnrealTargetPlatform.IOS) - { - var dylibPath = Path.Combine(ModuleDirectory, "libnakama", "ios-arm64", "libnakama-sdk.dylib"); - PublicDelayLoadDLLs.Add(dylibPath); - RuntimeDependencies.Add(dylibPath); - } - else if (Target.Platform == UnrealTargetPlatform.Android) - { - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakama", "armeabi-v7a", "libnakama-sdk.so")); - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakama", "arm64-v8a", "libnakama-sdk.so")); - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakama", "x86_64", "libnakama-sdk.so")); - - string relAPLPath = Utils.MakePathRelativeTo(Path.Combine(ModuleDirectory, "Nakama_APL.xml"), Target.RelativeEnginePath); - AdditionalPropertiesForReceipt.Add("AndroidPlugin", relAPLPath); - } - else - { - if (!libs.ContainsKey(Target.Platform)) - { - throw new BuildException("Unsupported platform"); - } - - var libFiles = libs[Target.Platform]; - - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakama", libFiles.Item1)); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", Path.GetFileName(libFiles.Item2)), Path.Combine(ModuleDirectory, "libnakama", libFiles.Item2)); - } - - PrivateDependencyModuleNames.AddRange(new string[]{ "Core", "HTTP" }); - PublicDependencyModuleNames.AddRange(new string[]{ "WebSockets" }); - } - - private bool IsX64Arch() - { -#if UE_5_2_OR_LATER - return Target.Architecture == UnrealArch.X64; -#else - // empty string is the "default architecture" which is x64 - return Target.Architecture == "" || Target.Architecture.StartsWith("x86_64") || Target.Architecture.StartsWith("x64"); -#endif - } -} diff --git a/Nakama/Source/NakamaCore/Nakama_APL.xml b/Nakama/Source/NakamaCore/Nakama_APL.xml deleted file mode 100644 index a7ae16122..000000000 --- a/Nakama/Source/NakamaCore/Nakama_APL.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Private/NakamaCore.cpp b/Nakama/Source/NakamaCore/Private/NakamaCore.cpp deleted file mode 100644 index 466a20c12..000000000 --- a/Nakama/Source/NakamaCore/Private/NakamaCore.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaCore.h" -#include "Modules/ModuleManager.h" -#include "UnrealLogSink.h" - -#define LOCTEXT_NAMESPACE "FNakamaCoreModule" - -using namespace NAKAMA_NAMESPACE; - -void FNakamaCoreModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - UE_LOG(LogNakama, Log, TEXT("Nakama module loaded")); -} - -void FNakamaCoreModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FNakamaCoreModule, NakamaCore) \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Private/NakamaCoreClientFactory.cpp b/Nakama/Source/NakamaCore/Private/NakamaCoreClientFactory.cpp deleted file mode 100644 index a04c5e0cb..000000000 --- a/Nakama/Source/NakamaCore/Private/NakamaCoreClientFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "NakamaCoreClientFactory.h" -#include "UnrealLogSink.h" -#include "http.h" -#include "UnrealWsTransport.h" -#include "WebSocketsModule.h" - -void NakamaCoreClientFactory::initLogging(Nakama::NLogLevel level) -{ - Nakama::NLogger::init(std::make_shared(), level); -} - -Nakama::NClientPtr NakamaCoreClientFactory::createNakamaClient(const Nakama::NClientParameters& parameters, Nakama::NLogLevel level) -{ - initLogging(level); - return Nakama::createRestClient(parameters, Nakama::NHttpTransportPtr(new Nakama::Unreal::UnrealHttpTransport())); -} - -Nakama::NRtClientPtr NakamaCoreClientFactory::createNakamaRtClient(const Nakama::NClientPtr& client) -{ - return client->createRtClient(Nakama::NRtTransportPtr(new Nakama::Unreal::UnrealWsTransport())); -} diff --git a/Nakama/Source/NakamaCore/Private/UnrealWsTransport.cpp b/Nakama/Source/NakamaCore/Private/UnrealWsTransport.cpp deleted file mode 100644 index 74d2631f9..000000000 --- a/Nakama/Source/NakamaCore/Private/UnrealWsTransport.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "UnrealWsTransport.h" - -#include -#include - -#include "Containers/StringConv.h" -#include "WebSocketsModule.h" - -DEFINE_LOG_CATEGORY_STATIC(NakamaWebsocket, All, All) - -namespace Nakama { -namespace Unreal { - -UnrealWsTransport::UnrealWsTransport() -{ -} - -void UnrealWsTransport::connect(const std::string& url, NRtTransportType type) -{ - FWebSocketsModule& websocketsModule = FModuleManager::LoadModuleChecked(TEXT("WebSockets")); - - WSConnection = websocketsModule.CreateWebSocket(UTF8_TO_TCHAR(url.c_str())); - if(!WSConnection.IsValid()) - { - UE_LOG(NakamaWebsocket, Error, TEXT("Create Websockets failed!")); - } - - TransportType = type; - - WSConnection->OnConnected().AddLambda([this]() - { - EventsQueue.Enqueue(MakeTuple(CallbackDispatch::OnConnected, std::string(""), NRtClientDisconnectInfo{})); - }); - WSConnection->OnConnectionError().AddLambda([this](const FString& Error) - { - EventsQueue.Enqueue(MakeTuple(CallbackDispatch::OnError, std::string(TCHAR_TO_UTF8(*Error)), NRtClientDisconnectInfo{})); - }); - - WSConnection->OnClosed().AddLambda([this](int32 StatusCode, const FString& Reason, bool bWasClean) - { - NRtClientDisconnectInfo info{ - static_cast(StatusCode), - TCHAR_TO_UTF8(*Reason), - bWasClean - }; - EventsQueue.Enqueue(MakeTuple(CallbackDispatch::OnDisconnected, std::string(""), std::move(info))); - }); - - // Unreal's WebSocket implementation doesn't use frame opcode to distinguish between binary and text data - // See: https://github.com/EpicGames/UnrealEngine/pull/7267 - // so let's do it ourselves. We don't have access to the opcode, so we use next best thing - expected message type - if (TransportType == NRtTransportType::Text) - { - WSConnection->OnMessage().AddLambda([this](const FString& MessageString) - { - EventsQueue.Enqueue(MakeTuple(CallbackDispatch::OnMessage, std::string(TCHAR_TO_UTF8(*MessageString)), NRtClientDisconnectInfo{})); - }); - } else - { - WSConnection->OnRawMessage().AddLambda([this](const void* Data , SIZE_T Size, SIZE_T BytesRemaining) - { - MessageBuffer.Append(static_cast(Data), Size); - if (BytesRemaining == 0) - { - EventsQueue.Enqueue(MakeTuple(CallbackDispatch::OnMessage, std::string(MessageBuffer.GetData(), MessageBuffer.Num()), NRtClientDisconnectInfo{})); - MessageBuffer.Reset(); - } - }); - } - - WSConnection->Connect(); -} - -void UnrealWsTransport::disconnect() -{ - if (!WSConnection.IsValid()) - { - return; - } - - // We don't want any more callbacks - WSConnection->OnClosed().Clear(); - WSConnection->OnConnectionError().Clear(); - WSConnection->OnRawMessage().Clear(); - WSConnection->OnConnected().Clear(); - WSConnection->OnMessage().Clear(); - WSConnection->OnMessageSent().Clear(); - WSConnection->Close(); - _connected = false; -} - -void UnrealWsTransport::tick() -{ - TTuple ev; - while (EventsQueue.Dequeue(ev)) - { - switch(ev.Get<0>()) - { - case CallbackDispatch::OnConnected: fireOnConnected(); break; - case CallbackDispatch::OnError: fireOnError(ev.Get<1>()); break; - case CallbackDispatch::OnDisconnected: fireOnDisconnected(ev.Get<2>()); break; - case CallbackDispatch::OnMessage: fireOnMessage(ev.Get<1>()); break; - default: checkNoEntry(); - } - } -} - -bool UnrealWsTransport::send(const NBytes& buf) -{ - if (!_connected) return false; - - WSConnection->Send(buf.data(), buf.length(), TransportType == NRtTransportType::Binary); - return true; -}; - -} -} diff --git a/Nakama/Source/NakamaCore/Private/UnrealWsTransport.h b/Nakama/Source/NakamaCore/Private/UnrealWsTransport.h deleted file mode 100644 index b1b3f39d0..000000000 --- a/Nakama/Source/NakamaCore/Private/UnrealWsTransport.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include -#include - -#include "Containers/Queue.h" -#include "IWebSocket.h" -#include "WebSocketsModule.h" - -namespace Nakama { -namespace Unreal { - -enum class CallbackDispatch { - OnConnected, - OnError, - OnDisconnected, - OnMessage, -}; - - -class UnrealWsTransport : public NRtTransportInterface -{ -public: - UnrealWsTransport(); - UnrealWsTransport(TSharedPtr webSocketsModule); - ~UnrealWsTransport() override { disconnect(); } - - // These 2 are now absolette and should be removed from the interface - void setActivityTimeout(uint32_t timeoutMs) override {}; - uint32_t getActivityTimeout() const override { return 0; }; - - void tick() override; - - void connect(const std::string& url, NRtTransportType type) override;; - - /** - * Close the connection with the server. - * - * Expectations from the implementation are: - * - It is possible to call connect after disconnect on the same instance - * - After disconnect returns any late messages arriving won't trigger messageCallback - * - It MUST NOT trigger fireOn* synchronous in this call - * - If connection is in progress and fireOnConnect is imminent, it MUST either schedule - * fireOnDisconnect to fire AFTER fireOnConnect or cancel it and fire neither. - * - */ - void disconnect() override; - - bool send(const NBytes& data) override; -private: - TSharedPtr WSConnection; - NRtTransportType TransportType = NRtTransportType::Binary; - // 0 indexed: fireOnConnected, fireOnerror, fireOnDisconnected, fireOnMessage - TQueue> EventsQueue; - TArray MessageBuffer; -}; -} -} diff --git a/Nakama/Source/NakamaCore/Private/http.cpp b/Nakama/Source/NakamaCore/Private/http.cpp deleted file mode 100644 index 98dba134c..000000000 --- a/Nakama/Source/NakamaCore/Private/http.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "http.h" -#include "HttpModule.h" -#include "Containers/ArrayBuilder.h" -#include "Interfaces/IHttpResponse.h" -#include "Misc/ScopeExit.h" - - -DEFINE_LOG_CATEGORY_STATIC(NakamaHttp, Warning, Warning); - - -namespace Nakama { -namespace Unreal { - -UnrealHttpTransport::UnrealHttpTransport() {} - -FString NHttpMethodToFString(NHttpReqMethod m) -{ - switch (m) - { - case NHttpReqMethod::GET: return TEXT("GET"); - case NHttpReqMethod::POST: return TEXT("POST"); - case NHttpReqMethod::DEL: return TEXT("DELETE"); - case NHttpReqMethod::PUT: return TEXT("PUT"); - } - checkNoEntry(); - return TEXT(""); -} - -void UnrealHttpTransport::request(const NHttpRequest& req, const NHttpResponseCallback& callback) -{ - auto HttpRequest = PrepareRequest(req); - HttpRequest->OnProcessRequestComplete().BindLambda([this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) - { - NHttpResponse NResponse; - if (bSuccess) - { - NResponse.statusCode = Response->GetResponseCode(); - auto& Content = Response->GetContent(); - if (Content.Num() != 0) - { - NResponse.body = std::string(reinterpret_cast(Content.GetData()), Content.Num()); - } - } else - { - NResponse.statusCode = InternalStatusCodes::CONNECTION_ERROR; - NResponse.errorMessage = "Connection failed"; - } - - CompletedRequestsCS.Lock(); - ON_SCOPE_EXIT { CompletedRequestsCS.Unlock(); }; - CompletedRequests.Emplace(std::move(Request), std::move(NResponse)); - }); - - if (callback) RegisterRequest(HttpRequest, callback); - HttpRequest->ProcessRequest(); -} - -FHttpRequestPtr UnrealHttpTransport::PrepareRequest(const NHttpRequest& req) const -{ - auto HttpRequest = FHttpModule::Get().CreateRequest(); - - FString URL = BaseURI; - URL.Append(req.path.data(), req.path.length()) - .Append(req.queryArgs.empty() ? "" : "?"); - - for (auto& p: req.queryArgs) - { - URL.Append(p.first.data(), p.first.length()) - .Append("=") - .Append(p.second.data(), p.second.length()) - .Append("&"); - } - - HttpRequest->SetURL(URL); - HttpRequest->SetVerb(NHttpMethodToFString(req.method)); - - for (auto& h: req.headers) - { - HttpRequest->SetHeader(UTF8_TO_TCHAR(h.first.c_str()), UTF8_TO_TCHAR(h.second.c_str())); - } - - if (!req.body.empty()) - { - HttpRequest->SetContent(TArray(reinterpret_cast(req.body.data()), req.body.length())); - } - HttpRequest->SetTimeout(5); - - return HttpRequest; -} - -void UnrealHttpTransport::RegisterRequest(FHttpRequestPtr Request, const NHttpResponseCallback& callback) -{ - PendingRequestsCS.Lock(); - ON_SCOPE_EXIT { PendingRequestsCS.Unlock(); }; - PendingRequests.Emplace(std::move(Request), std::move(callback)); -} - -void UnrealHttpTransport::tick() -{ - CompletedRequestsCS.Lock(); - ON_SCOPE_EXIT { CompletedRequestsCS.Unlock(); }; - for (const auto& item: CompletedRequests) - { - NHttpResponseCallback callback = nullptr; - - { - PendingRequestsCS.Lock(); - ON_SCOPE_EXIT { PendingRequestsCS.Unlock(); }; - if (!PendingRequests.RemoveAndCopyValue(item.Key, callback)) - { - UE_LOG(NakamaHttp, Warning, TEXT("Completed request doesn't have matching PendingRequests entry. No callback will be called.")); - continue; - } - } - callback(std::make_shared(std::move(item.Value))); - } - CompletedRequests.Reset(); -} - -void UnrealHttpTransport::cancelAllRequests() -{ - PendingRequestsCS.Lock(); - ON_SCOPE_EXIT { PendingRequestsCS.Unlock(); }; - for (auto& p: PendingRequests) - { - auto Request = p.Key.Get(); - Request->OnProcessRequestComplete().Unbind(); - Request->CancelRequest(); - - NHttpResponsePtr responsePtr(new NHttpResponse{InternalStatusCodes::CANCELLED_BY_USER, std::string() , std::string()}); - (p.Value)(std::move(responsePtr)); - } - PendingRequests.Reset(); -} - - -} -} diff --git a/Nakama/Source/NakamaCore/Private/http.h b/Nakama/Source/NakamaCore/Private/http.h deleted file mode 100644 index e89283806..000000000 --- a/Nakama/Source/NakamaCore/Private/http.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include "Interfaces/IHttpRequest.h" - -namespace Nakama { -namespace Unreal { -class UnrealHttpTransport : public NHttpTransportInterface { -public: - UnrealHttpTransport(); - virtual ~UnrealHttpTransport() override = default; - void setBaseUri(const std::string &uri) override { BaseURI = UTF8_TO_TCHAR(uri.c_str()); } - void tick() override; - void request(const NHttpRequest &req, const NHttpResponseCallback &callback = nullptr) override; - void cancelAllRequests() override; -private: - FHttpRequestPtr PrepareRequest(const NHttpRequest& req) const; - void RegisterRequest(FHttpRequestPtr Request, const NHttpResponseCallback& callback); - - TMap PendingRequests; - FCriticalSection PendingRequestsCS; - - TMap CompletedRequests; - FCriticalSection CompletedRequestsCS; - - FString BaseURI; -}; -} -} diff --git a/Nakama/Source/NakamaCore/Private/mod.cpp b/Nakama/Source/NakamaCore/Private/mod.cpp deleted file mode 100644 index 8ebea68b6..000000000 --- a/Nakama/Source/NakamaCore/Private/mod.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "UnrealLogSink.h" -#include "NakamaCore.h" -#include "http.h" -#include "UnrealWsTransport.h" - -#include "CoreMinimal.h" - -DEFINE_LOG_CATEGORY(LogNakama); - -namespace Nakama { -namespace Unreal { - -void initLogging(NLogLevel level) -{ - Nakama::NLogger::init(std::make_shared(), level); -} - -NClientPtr createNakamaClient(const NClientParameters& parameters, NLogLevel level) -{ - initLogging(level); - return Nakama::createRestClient(parameters, NHttpTransportPtr(new UnrealHttpTransport())); -} - -NRtClientPtr createNakamaRtClient(const NClientPtr& client) -{ - return client->createRtClient(NRtTransportPtr(new UnrealWsTransport())); -} - -} -} \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/NakamaCore.h b/Nakama/Source/NakamaCore/Public/NakamaCore.h deleted file mode 100644 index 18c01d5dd..000000000 --- a/Nakama/Source/NakamaCore/Public/NakamaCore.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "Modules/ModuleManager.h" -#include - -NAKAMA_NAMESPACE_BEGIN - -namespace Unreal { - -void initLogging(NLogLevel level); -NClientPtr createNakamaClient(const NClientParameters& parameters, NLogLevel level); -NRtClientPtr createNakamaRtClient(const NClientPtr& client, int32_t port); -NRtClientPtr createNakamaRtClient(const NClientPtr& client, const RtClientParameters& params); - -} - -NAKAMA_NAMESPACE_END - -class FNakamaCoreModule : public IModuleInterface -{ -public: - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; - -#pragma once diff --git a/Nakama/Source/NakamaCore/Public/NakamaCoreClientFactory.h b/Nakama/Source/NakamaCore/Public/NakamaCoreClientFactory.h deleted file mode 100644 index 8e78fee90..000000000 --- a/Nakama/Source/NakamaCore/Public/NakamaCoreClientFactory.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include "WebSocketsModule.h" - -class NAKAMACORE_API NakamaCoreClientFactory { - -public: - static void initLogging(Nakama::NLogLevel level); - static Nakama::NClientPtr createNakamaClient(const Nakama::NClientParameters& parameters, Nakama::NLogLevel level); - static Nakama::NRtClientPtr createNakamaRtClient(const Nakama::NClientPtr& client); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/UnrealLogSink.h b/Nakama/Source/NakamaCore/Public/UnrealLogSink.h deleted file mode 100644 index 7c71e0a50..000000000 --- a/Nakama/Source/NakamaCore/Public/UnrealLogSink.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include - -DECLARE_LOG_CATEGORY_EXTERN(LogNakama, Log, All); - -NAKAMA_NAMESPACE_BEGIN - - class NUnrealLogSink : public NLogSinkInterface - { - public: - ~NUnrealLogSink() override {} - - void log(NLogLevel level, const std::string& message, const char* func) override - { - std::string tmp; - - if (func && func[0]) - { - tmp.append("[").append(func).append("] "); - } - - tmp.append(message); - - switch (level) - { - case NLogLevel::Debug: - UE_LOG(LogTemp, Verbose, TEXT("%s"), UTF8_TO_TCHAR(tmp.c_str())); - break; - case NLogLevel::Info: - UE_LOG(LogTemp, Log, TEXT("%s"), UTF8_TO_TCHAR(tmp.c_str())); - break; - case NLogLevel::Warn: - UE_LOG(LogTemp, Warning, TEXT("%s"), UTF8_TO_TCHAR(tmp.c_str())); - break; - case NLogLevel::Error: - UE_LOG(LogTemp, Error, TEXT("%s"), UTF8_TO_TCHAR(tmp.c_str())); - break; - case NLogLevel::Fatal: - UE_LOG(LogTemp, Fatal, TEXT("%s"), UTF8_TO_TCHAR(tmp.c_str())); - break; - } - } - - void flush() override {} - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/ClientFactory.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/ClientFactory.h deleted file mode 100644 index c37d100ae..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/ClientFactory.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - struct NClientParameters - { - /// The key used to authenticate with the server without a session. Defaults to "defaultkey". - std::string serverKey = "defaultkey"; - - /// The host address of the server. Defaults to "127.0.0.1". - std::string host = "127.0.0.1"; - - /// The port number of the server. - /// Default server ports (can be changed in the server config): - /// 7349 - gRPC API - /// 7350 - HTTP API - /// 443 - gRPC & HTTP API if SSL is enabled - int32_t port = DEFAULT_PORT; - - /// Set connection strings to use the secure mode with the server. Defaults to false. - /// The server must be configured to make use of this option. With HTTP, GRPC, and WebSockets the server must - /// be configured with an SSL certificate or use a load balancer which performs SSL termination. - /// For rUDP you must configure the server to expose it's IP address so it can be bundled within session tokens. - /// See the server documentation for more information. - bool ssl = false; - - /// Platform specific parameters -#ifdef DEFAULT_PLATFORM_PARAMS - NPlatformParameters platformParams = {}; -#else - NPlatformParameters platformParams; -#endif - }; - - - /// DefaultClientParameters is deprectaed, use NClientParameters instead - using DefaultClientParameters = NClientParameters; - - /** - * Creates a default client to interact with Nakama server. - * - * @param parameters the client parameters - */ -#if !defined(WITH_EXTERNAL_HTTP) || defined(BUILD_GRPC_CLIENT) - NAKAMA_API NClientPtr createDefaultClient(const NClientParameters& parameters); -#endif - -#ifdef BUILD_GRPC_CLIENT - /** - * Creates the gRPC client to interact with Nakama server. - * - * @param parameters the client parameters - */ - NAKAMA_API NClientPtr createGrpcClient(const NClientParameters& parameters); -#endif - - /** - * Creates the REST client (HTTP/1.1) to interact with Nakama server. - * - * @param parameters the client parameters - * @param httpTransport optional, the HTTP client. If not set then default HTTP transport will be used. - */ - NAKAMA_API NClientPtr createRestClient(const NClientParameters& parameters, NHttpTransportPtr httpTransport = nullptr); - - /** - * Creates default HTTP transport using C++ REST SDK. - */ -#ifndef WITH_EXTERNAL_HTTP - NAKAMA_API NHttpTransportPtr createDefaultHttpTransport(const NPlatformParameters& platformParams); -#endif - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NClientInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NClientInterface.h deleted file mode 100644 index 9f662875b..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NClientInterface.h +++ /dev/null @@ -1,2136 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - using ErrorCallback = std::function; - - /** - * A client interface to interact with Nakama server. - */ - class NAKAMA_API NClientInterface - { - public: - virtual ~NClientInterface() {} - - /** - * Set default error callback. - * - * Will be called if a request fails and no error callback was set for the request. - * - * @param errorCallback The error callback. - */ - virtual void setErrorCallback(ErrorCallback errorCallback) = 0; - - /** - * Set user data. - * - * Client just holds this data so you can receive it later when you need it. - * - * @param userData The user data. - */ - virtual void setUserData(void* userData) = 0; - - /** - * Get user data. - * - * @return The user data. - */ - virtual void* getUserData() const = 0; - - /** - * Disconnects the client. This function kills all outgoing exchanges immediately without waiting. - */ - virtual void disconnect() = 0; - - /** - * Pumps requests queue in your thread. - * Call it periodically, each 50 ms is ok. - */ - virtual void tick() = 0; - -#if !defined(WITH_EXTERNAL_WS) && !defined(BUILD_IO_EXTERNAL) - /** - * Create a new real-time client with parameters from client. - * @return a new NRtClient instance. - */ - virtual NRtClientPtr createRtClient() = 0; -#endif - - /** - * Create a new real-time client with parameters from client. - * - * @param parameters The real-time client parameters. - * @param transport The websocket transport. - * @return a new NRtClient instance. - */ - virtual NRtClientPtr createRtClient(NRtTransportPtr transport) = 0; - - /** - * Authenticate a user with a device id. - * - * @param id A device identifier usually obtained from a platform API. - * @param username A username used to create the user. Defaults to empty string. - * @param create True if the user should be created when authenticated. Defaults to false. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateDevice( - const std::string& id, - const opt::optional& username = opt::nullopt, - const opt::optional& create = opt::nullopt, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with an email and password. - * - * @param email The email address of the user. - * @param password The password for the user. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateEmail( - const std::string& email, - const std::string& password, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with a Facebook auth token. - * - * @param accessToken An OAuth access token from the Facebook SDK. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param importFriends True if the Facebook friends should be imported. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateFacebook( - const std::string& accessToken, - const std::string& username = std::string(), - bool create = false, - bool importFriends = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with a Google auth token. - * - * @param accessToken An OAuth access token from the Google SDK. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateGoogle( - const std::string& accessToken, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with Apple Game Center. - * - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateGameCenter( - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with Apple Sign In. - * - * @param token The ID token received from Apple to validate. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateApple( - const std::string& token, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with a custom id. - * - * @param id A custom identifier usually obtained from an external authentication service. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateCustom( - const std::string& id, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with a Steam auth token. - * - * @param token An authentication token from the Steam network. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual void authenticateSteam( - const std::string& token, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param session The session of the user. - **/ - virtual void authenticateRefresh( - NSessionPtr session, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr) = 0; - - /** - * Link a Facebook profile to a user account. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Facebook SDK. - * @param importFriends True if the Facebook friends should be imported. - */ - virtual void linkFacebook( - NSessionPtr session, - const std::string& accessToken, - const opt::optional& importFriends = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link an email with password to the user account owned by the session. - * - * @param session The session of the user. - * @param email The email address of the user. - * @param password The password for the user. - */ - virtual void linkEmail( - NSessionPtr session, - const std::string& email, - const std::string& password, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link a device id to the user account owned by the session. - * - * @param session The session of the user. - * @param id A device identifier usually obtained from a platform API. - */ - virtual void linkDevice( - NSessionPtr session, - const std::string& id, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link a Google profile to a user account. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Google SDK. - */ - virtual void linkGoogle( - NSessionPtr session, - const std::string& accessToken, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link a Game Center profile to a user account. - * - * @param session The session of the user. - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - */ - virtual void linkGameCenter( - NSessionPtr session, - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link an Apple ID to the social profiles on the current user's account. - * - * @param session The session of the user. - * @param token The ID token received from Apple. - */ - virtual void linkApple( - NSessionPtr session, - const std::string& token, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link a Steam profile to a user account. - * - * @param session The session of the user. - * @param token An authentication token from the Steam network. - */ - virtual void linkSteam( - NSessionPtr session, - const std::string& token, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Link a custom id to the user account owned by the session. - * - * @param session The session of the user. - * @param id A custom identifier usually obtained from an external authentication service. - */ - virtual void linkCustom( - NSessionPtr session, - const std::string& id, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a Facebook profile from the user account owned by the session. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Facebook SDK. - */ - virtual void unlinkFacebook( - NSessionPtr session, - const std::string& accessToken, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink an email with password from the user account owned by the session. - * - * @param session The session of the user. - * @param email The email address of the user. - * @param password The password for the user. - */ - virtual void unlinkEmail( - NSessionPtr session, - const std::string& email, - const std::string& password, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a Google profile from the user account owned by the session. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Google SDK. - */ - virtual void unlinkGoogle( - NSessionPtr session, - const std::string& accessToken, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a Game Center profile from the user account owned by the session. - * - * @param session The session of the user. - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - */ - virtual void unlinkGameCenter( - NSessionPtr session, - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a Apple profile from the user account owned by the session. - * - * @param session The session of the user. - * @param token An Apple authentication token. - */ - virtual void unlinkApple( - NSessionPtr session, - const std::string& token, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a Steam profile from the user account owned by the session. - * - * @param session The session of the user. - * @param token An authentication token from the Steam network. - */ - virtual void unlinkSteam( - NSessionPtr session, - const std::string& token, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a device id from the user account owned by the session. - * - * @param session The session of the user. - * @param id A device identifier usually obtained from a platform API. - */ - virtual void unlinkDevice( - NSessionPtr session, - const std::string& id, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unlink a custom id from the user account owned by the session. - * - * @param session The session of the user. - * @param id A custom identifier usually obtained from an external authentication service. - */ - virtual void unlinkCustom( - NSessionPtr session, - const std::string& id, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Import Facebook friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Facebook. This function can be used to be - * explicit with the import operation. - * - * @param session The session of the user. - * @param token An OAuth access token from the Facebook SDK. - * @param reset True if the Facebook friend import for the user should be reset. - */ - virtual void importFacebookFriends( - NSessionPtr session, - const std::string& token, - const opt::optional& reset = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Fetch the user account owned by the session. - * - * @param session The session of the user. - */ - virtual void getAccount( - NSessionPtr session, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Update the current user's account on the server. - * - * @param session The session for the user. - * @param username The new username for the user. - * @param displayName A new display name for the user. - * @param avatarUrl A new avatar url for the user. - * @param langTag A new language tag in BCP-47 format for the user. - * @param location A new location for the user. - * @param timezone New timezone information for the user. - */ - virtual void updateAccount( - NSessionPtr session, - const opt::optional& username = opt::nullopt, - const opt::optional& displayName = opt::nullopt, - const opt::optional& avatarUrl = opt::nullopt, - const opt::optional& langTag = opt::nullopt, - const opt::optional& location = opt::nullopt, - const opt::optional& timezone = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param session The session of the user. - * @param ids List of user IDs. - * @param usernames List of usernames. - * @param facebookIds List of Facebook IDs. - */ - virtual void getUsers( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {}, - const std::vector& facebookIds = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Add one or more friends by id. - * - * @param session The session of the user. - * @param ids The ids of the users to add or invite as friends. - * @param usernames The usernames of the users to add as friends. - */ - virtual void addFriends( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Delete one more or users by id or username from friends. - * - * @param session The session of the user. - * @param ids the user ids to remove as friends. - * @param usernames The usernames to remove as friends. - */ - virtual void deleteFriends( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Block one or more friends by id. - * - * @param session The session of the user. - * @param ids The ids of the users to block. - * @param usernames The usernames of the users to block. - */ - virtual void blockFriends( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List of friends of the current user. - * - * @param session The session of the user. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The friend state to list. - * @param cursor An optional next page cursor. - */ - virtual void listFriends( - NSessionPtr session, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "", - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Create a group. - * - * @param session The session of the user. - * @param name The name for the group. - * @param description A description for the group. - * @param avatarUrl An avatar url for the group. - * @param langTag A language tag in BCP-47 format for the group. - * @param open True if the group should have open membership. - * @param maxCount Maximum number of group members. - */ - virtual void createGroup( - NSessionPtr session, - const std::string& name, - const std::string& description = "", - const std::string& avatarUrl = "", - const std::string& langTag = "", - bool open = false, - const opt::optional& maxCount = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Delete a group by id. - * - * @param session The session of the user. - * @param groupId The group id to to remove. - */ - virtual void deleteGroup( - NSessionPtr session, - const std::string& groupId, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Add one or more users to the group. - * - * @param session The session of the user. - * @param groupId The id of the group to add users into. - * @param ids The ids of the users to add or invite to the group. - */ - virtual void addGroupUsers( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List all users part of the group. - * - * @param session The session of the user. - * @param groupId The id of the group. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual void listGroupUsers( - NSessionPtr session, - const std::string& groupId, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "", - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Kick one or more users from the group. - * - * @param session The session of the user. - * @param groupId The id of the group. - * @param ids The ids of the users to kick. - */ - virtual void kickGroupUsers( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Join a group if it has open membership or request to join it. - * - * @param session The session of the user. - * @param groupId The id of the group to join. - */ - virtual void joinGroup( - NSessionPtr session, - const std::string& groupId, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Leave a group by id. - * - * @param session The session of the user. - * @param groupId The id of the group to leave. - */ - virtual void leaveGroup( - NSessionPtr session, - const std::string& groupId, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List groups on the server. - * - * @param session The session of the user. - * @param name The name filter to apply to the group list. - * @param limit The number of groups to list. - * @param cursor A cursor for the current position in the groups to list. - */ - virtual void listGroups( - NSessionPtr session, - const std::string& name, - int32_t limit = 0, - const std::string& cursor = "", - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List of groups the current user is a member of. - * - * @param session The session of the user. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual void listUserGroups( - NSessionPtr session, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "", - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List groups a user is a member of. - * - * @param session The session of the user. - * @param userId The id of the user whose groups to list. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual void listUserGroups( - NSessionPtr session, - const std::string& userId, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "", - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Promote a set of users in a group to the next role up. - * - * @param session The session of the user. - * @param groupId The group ID to promote in. - * @param ids The ids of the users to promote. - */ - virtual void promoteGroupUsers( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Demote a set of users in a group to the next role down. - * - * @param session The session of the user. - * @param groupId The group ID to demote in. - * @param ids The ids of the users to demote. - */ - virtual void demoteGroupUsers( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Update a group. - * - * The user must have the correct access permissions for the group. - * - * @param session The session of the user. - * @param groupId The id of the group to update. - * @param name A new name for the group. - * @param description A new description for the group. - * @param avatarUrl A new avatar url for the group. - * @param langTag A new language tag in BCP-47 format for the group. - * @param open True if the group should have open membership. - */ - virtual void updateGroup( - NSessionPtr session, - const std::string& groupId, - const opt::optional& name = opt::nullopt, - const opt::optional& description = opt::nullopt, - const opt::optional& avatarUrl = opt::nullopt, - const opt::optional& langTag = opt::nullopt, - const opt::optional& open = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List records from a leaderboard. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard to list. - * @param ownerIds Record owners to fetch with the list of records. - * @param limit The number of records to list. - * @param cursor A cursor for the current position in the leaderboard records to list. - */ - virtual void listLeaderboardRecords( - NSessionPtr session, - const std::string& leaderboardId, - const std::vector& ownerIds = {}, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List leaderboard records from a given leaderboard around the owner. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard to list. - * @param ownerId The owner to retrieve records around. - * @param limit Max number of records to return. Between 1 and 100. - */ - virtual void listLeaderboardRecordsAroundOwner( - NSessionPtr session, - const std::string& leaderboardId, - const std::string& ownerId, - const opt::optional& limit = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Write a record to a leaderboard. - * - * @param session The session for the user. - * @param leaderboardId The id of the leaderboard to write. - * @param score The score for the leaderboard record. - * @param subscore The subscore for the leaderboard record. - * @param metadata The metadata for the leaderboard record. - */ - virtual void writeLeaderboardRecord( - NSessionPtr session, - const std::string& leaderboardId, - std::int64_t score, - const opt::optional& subscore = opt::nullopt, - const opt::optional& metadata = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * A request to submit a score to a tournament. - * - * @param session The session for the user. - * @param tournamentId The tournament ID to write the record for. - * @param score The score value to submit. - * @param subscore An optional secondary value. - * @param metadata A JSON object of additional properties. - */ - virtual void writeTournamentRecord( - NSessionPtr session, - const std::string& tournamentId, - std::int64_t score, - const opt::optional& subscore = opt::nullopt, - const opt::optional& metadata = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Delete a leaderboard record. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard with the record to be deleted. - */ - virtual void deleteLeaderboardRecord( - NSessionPtr session, - const std::string& leaderboardId, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Fetch a list of matches active on the server. - * - * @param session The session of the user. - * @param min_size The minimum number of match participants. - * @param max_size The maximum number of match participants. - * @param limit The number of matches to list. - * @param label The label to filter the match list on. - * @param authoritative true to include authoritative matches. - */ - virtual void listMatches( - NSessionPtr session, - const opt::optional& min_size = opt::nullopt, - const opt::optional& max_size = opt::nullopt, - const opt::optional& limit = opt::nullopt, - const opt::optional& label = opt::nullopt, - const opt::optional& query = opt::nullopt, - const opt::optional& authoritative = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List notifications for the user with an optional cursor. - * - * @param session The session of the user. - * @param limit The number of notifications to list. - * @param cacheableCursor A cursor for the current position in notifications to list. - */ - virtual void listNotifications( - NSessionPtr session, - const opt::optional& limit = opt::nullopt, - const opt::optional& cacheableCursor = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Delete one or more notifications by id. - * - * @param session The session of the user. - * @param notificationIds The notification ids to remove. - */ - virtual void deleteNotifications( - NSessionPtr session, - const std::vector& notificationIds, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List messages from a chat channel. - * - * @param session The session of the user. - * @param channelId A channel identifier. - * @param limit The number of chat messages to list. - * @param cursor A cursor for the current position in the messages history to list. - * @param forward Fetch messages forward from the current cursor (or the start). - */ - virtual void listChannelMessages( - NSessionPtr session, - const std::string& channelId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - const opt::optional& forward = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List active/upcoming tournaments based on given filters. - * - * @param session The session of the user. - * @param categoryStart The start of the categories to include. Defaults to 0. - * @param categoryEnd The end of the categories to include. Defaults to 128. - * @param startTime The start time for tournaments. Defaults to current Unix time. - * @param endTime The end time for tournaments. Defaults to +1 year from current Unix time. - * @param limit Max number of records to return. Between 1 and 100. - * @param cursor A next page cursor for listings. - */ - virtual void listTournaments( - NSessionPtr session, - const opt::optional& categoryStart = opt::nullopt, - const opt::optional& categoryEnd = opt::nullopt, - const opt::optional& startTime = opt::nullopt, - const opt::optional& endTime = opt::nullopt, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List tournament records from a given tournament. - * - * @param session The session of the user. - * @param tournamentId The ID of the tournament to list for. - * @param limit Max number of records to return. Between 1 and 100. - * @param cursor A next or previous page cursor. - * @param ownerIds One or more owners to retrieve records for. - */ - virtual void listTournamentRecords( - NSessionPtr session, - const std::string& tournamentId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - const std::vector& ownerIds = {}, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List tournament records from a given tournament around the owner. - * - * @param session The session of the user. - * @param tournamentId The ID of the tournament to list for. - * @param ownerId The owner to retrieve records around. - * @param limit Max number of records to return. Between 1 and 100. - */ - virtual void listTournamentRecordsAroundOwner( - NSessionPtr session, - const std::string& tournamentId, - const std::string& ownerId, - const opt::optional& limit = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Join a tournament if it has open membership or request to join it. - * - * @param session The session of the user. - * @param tournamentId The id of the tournament to join. - */ - virtual void joinTournament( - NSessionPtr session, - const std::string& tournamentId, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List storage objects in a collection which have public read access. - * - * @param session The session of the user. - * @param collection The collection to list over. - * @param limit The number of objects to list. - * @param cursor A cursor to paginate over the collection. - */ - virtual void listStorageObjects( - NSessionPtr session, - const std::string& collection, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * List storage objects in a collection which belong to a specific user and have public read access. - * - * @param session The session of the user. - * @param collection The collection to list over. - * @param userId The user ID of the user to list objects for. - * @param limit The number of objects to list. - * @param cursor A cursor to paginate over the collection. - */ - virtual void listUsersStorageObjects( - NSessionPtr session, - const std::string& collection, - const std::string& userId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Write objects to the storage engine. - * - * @param session The session of the user. - * @param objects The objects to write. - */ - virtual void writeStorageObjects( - NSessionPtr session, - const std::vector& objects, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Read one or more objects from the storage engine. - * - * @param session The session of the user. - * @param objectIds The objects to read. - */ - virtual void readStorageObjects( - NSessionPtr session, - const std::vector& objectIds, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Delete one or more storage objects. - * - * @param session The session of the user. - * @param objectIds The ids of the objects to delete. - */ - virtual void deleteStorageObjects( - NSessionPtr session, - const std::vector& objectIds, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Execute a server framework function with an input payload on the server. - * - * @param session The session of the user. - * @param id The id of the function to execute on the server. - * @param payload The payload to send with the function call. - */ - virtual void rpc( - NSessionPtr session, - const std::string& id, - const opt::optional& payload = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Execute a server framework function with an input payload on the server. - * - * @param http_key The server's runtime HTTP key. - * @param id The id of the function to execute on the server. - * @param payload The payload to send with the function call. - */ - virtual void rpc( - const std::string& http_key, - const std::string& id, - const opt::optional& payload = opt::nullopt, - std::function successCallback = nullptr, - ErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Authenticate a user with a device id. - * - * @param id A device identifier usually obtained from a platform API. - * @param username A username used to create the user. Defaults to empty string. - * @param create True if the user should be created when authenticated. Defaults to false. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateDeviceAsync( - const std::string& id, - const opt::optional& username = opt::nullopt, - const opt::optional& create = opt::nullopt, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with an email and password. - * - * @param email The email address of the user. - * @param password The password for the user. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateEmailAsync( - const std::string& email, - const std::string& password, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with a Facebook auth token. - * - * @param accessToken An OAuth access token from the Facebook SDK. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param importFriends True if the Facebook friends should be imported. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateFacebookAsync( - const std::string& accessToken, - const std::string& username = std::string(), - bool create = false, - bool importFriends = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with a Google auth token. - * - * @param accessToken An OAuth access token from the Google SDK. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateGoogleAsync( - const std::string& accessToken, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with Apple Game Center. - * - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateGameCenterAsync( - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with Apple Sign In. - * - * @param token The ID token received from Apple to validate. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateAppleAsync( - const std::string& token, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with a custom id. - * - * @param id A custom identifier usually obtained from an external authentication service. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateCustomAsync( - const std::string& id, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Authenticate a user with a Steam auth token. - * - * @param token An authentication token from the Steam network. - * @param username A username used to create the user. - * @param create True if the user should be created when authenticated. - * @param vars Extra information that will be bundled in the session token. - */ - virtual std::future authenticateSteamAsync( - const std::string& token, - const std::string& username = std::string(), - bool create = false, - const NStringMap& vars = {} - ) = 0; - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param session The session of the user. - **/ - virtual std::future authenticateRefreshAsync(NSessionPtr session) = 0; - - /** - * Link a Facebook profile to a user account. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Facebook SDK. - * @param importFriends True if the Facebook friends should be imported. - */ - virtual std::future linkFacebookAsync( - NSessionPtr session, - const std::string& accessToken, - const opt::optional& importFriends = opt::nullopt - ) = 0; - - /** - * Link an email with password to the user account owned by the session. - * - * @param session The session of the user. - * @param email The email address of the user. - * @param password The password for the user. - */ - virtual std::future linkEmailAsync( - NSessionPtr session, - const std::string& email, - const std::string& password - ) = 0; - - /** - * Link a device id to the user account owned by the session. - * - * @param session The session of the user. - * @param id A device identifier usually obtained from a platform API. - */ - virtual std::future linkDeviceAsync( - NSessionPtr session, - const std::string& id - ) = 0; - - /** - * Link a Google profile to a user account. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Google SDK. - */ - virtual std::future linkGoogleAsync( - NSessionPtr session, - const std::string& accessToken - ) = 0; - - /** - * Link a Game Center profile to a user account. - * - * @param session The session of the user. - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - */ - virtual std::future linkGameCenterAsync( - NSessionPtr session, - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl - ) = 0; - - /** - * Link an Apple ID to the social profiles on the current user's account. - * - * @param session The session of the user. - * @param token The ID token received from Apple. - */ - virtual std::future linkAppleAsync( - NSessionPtr session, - const std::string& token - ) = 0; - - /** - * Link a Steam profile to a user account. - * - * @param session The session of the user. - * @param token An authentication token from the Steam network. - */ - virtual std::future linkSteamAsync( - NSessionPtr session, - const std::string& token - ) = 0; - - /** - * Link a custom id to the user account owned by the session. - * - * @param session The session of the user. - * @param id A custom identifier usually obtained from an external authentication service. - */ - virtual std::future linkCustomAsync( - NSessionPtr session, - const std::string& id - ) = 0; - - /** - * Unlink a Facebook profile from the user account owned by the session. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Facebook SDK. - */ - virtual std::future unlinkFacebookAsync( - NSessionPtr session, - const std::string& accessToken - ) = 0; - - /** - * Unlink an email with password from the user account owned by the session. - * - * @param session The session of the user. - * @param email The email address of the user. - * @param password The password for the user. - */ - virtual std::future unlinkEmailAsync( - NSessionPtr session, - const std::string& email, - const std::string& password - ) = 0; - - /** - * Unlink a Google profile from the user account owned by the session. - * - * @param session The session of the user. - * @param accessToken An OAuth access token from the Google SDK. - */ - virtual std::future unlinkGoogleAsync( - NSessionPtr session, - const std::string& accessToken - ) = 0; - - /** - * Unlink a Game Center profile from the user account owned by the session. - * - * @param session The session of the user. - * @param playerId The player id of the user in Game Center. - * @param bundleId The bundle id of the Game Center application. - * @param timestampSeconds The date and time that the signature was created. - * @param salt A random NSString used to compute the hash and keep it randomized. - * @param signature The verification signature data generated. - * @param publicKeyUrl The URL for the public encryption key. - */ - virtual std::future unlinkGameCenterAsync( - NSessionPtr session, - const std::string& playerId, - const std::string& bundleId, - NTimestamp timestampSeconds, - const std::string& salt, - const std::string& signature, - const std::string& publicKeyUrl - ) = 0; - - /** - * Unlink a Apple profile from the user account owned by the session. - * - * @param session The session of the user. - * @param token An Apple authentication token. - */ - virtual std::future unlinkAppleAsync( - NSessionPtr session, - const std::string& token - ) = 0; - - /** - * Unlink a Steam profile from the user account owned by the session. - * - * @param session The session of the user. - * @param token An authentication token from the Steam network. - */ - virtual std::future unlinkSteamAsync( - NSessionPtr session, - const std::string& token - ) = 0; - - /** - * Unlink a device id from the user account owned by the session. - * - * @param session The session of the user. - * @param id A device identifier usually obtained from a platform API. - */ - virtual std::future unlinkDeviceAsync( - NSessionPtr session, - const std::string& id - ) = 0; - - /** - * Unlink a custom id from the user account owned by the session. - * - * @param session The session of the user. - * @param id A custom identifier usually obtained from an external authentication service. - */ - virtual std::future unlinkCustomAsync( - NSessionPtr session, - const std::string& id - ) = 0; - - /** - * Import Facebook friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Facebook. This function can be used to be - * explicit with the import operation. - * - * @param session The session of the user. - * @param token An OAuth access token from the Facebook SDK. - * @param reset True if the Facebook friend import for the user should be reset. - */ - virtual std::future importFacebookFriendsAsync( - NSessionPtr session, - const std::string& token, - const opt::optional& reset = opt::nullopt - ) = 0; - - /** - * Fetch the user account owned by the session. - * - * @param session The session of the user. - */ - virtual std::future getAccountAsync( - NSessionPtr session - ) = 0; - - /** - * Update the current user's account on the server. - * - * @param session The session for the user. - * @param username The new username for the user. - * @param displayName A new display name for the user. - * @param avatarUrl A new avatar url for the user. - * @param langTag A new language tag in BCP-47 format for the user. - * @param location A new location for the user. - * @param timezone New timezone information for the user. - */ - virtual std::future updateAccountAsync( - NSessionPtr session, - const opt::optional& username = opt::nullopt, - const opt::optional& displayName = opt::nullopt, - const opt::optional& avatarUrl = opt::nullopt, - const opt::optional& langTag = opt::nullopt, - const opt::optional& location = opt::nullopt, - const opt::optional& timezone = opt::nullopt - ) = 0; - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param session The session of the user. - * @param ids List of user IDs. - * @param usernames List of usernames. - * @param facebookIds List of Facebook IDs. - */ - virtual std::future getUsersAsync( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {}, - const std::vector& facebookIds = {} - ) = 0; - - /** - * Add one or more friends by id. - * - * @param session The session of the user. - * @param ids The ids of the users to add or invite as friends. - * @param usernames The usernames of the users to add as friends. - */ - virtual std::future addFriendsAsync( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {} - ) = 0; - - /** - * Delete one more or users by id or username from friends. - * - * @param session The session of the user. - * @param ids the user ids to remove as friends. - * @param usernames The usernames to remove as friends. - */ - virtual std::future deleteFriendsAsync( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {} - ) = 0; - - /** - * Block one or more friends by id. - * - * @param session The session of the user. - * @param ids The ids of the users to block. - * @param usernames The usernames of the users to block. - */ - virtual std::future blockFriendsAsync( - NSessionPtr session, - const std::vector& ids, - const std::vector& usernames = {} - ) = 0; - - /** - * List of friends of the current user. - * - * @param session The session of the user. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The friend state to list. - * @param cursor An optional next page cursor. - */ - virtual std::future listFriendsAsync( - NSessionPtr session, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "" - ) = 0; - - /** - * Create a group. - * - * @param session The session of the user. - * @param name The name for the group. - * @param description A description for the group. - * @param avatarUrl An avatar url for the group. - * @param langTag A language tag in BCP-47 format for the group. - * @param open True if the group should have open membership. - * @param maxCount Maximum number of group members. - */ - virtual std::future createGroupAsync( - NSessionPtr session, - const std::string& name, - const std::string& description = "", - const std::string& avatarUrl = "", - const std::string& langTag = "", - bool open = false, - const opt::optional& maxCount = {} - ) = 0; - - /** - * Delete a group by id. - * - * @param session The session of the user. - * @param groupId The group id to to remove. - */ - virtual std::future deleteGroupAsync( - NSessionPtr session, - const std::string& groupId - ) = 0; - - /** - * Add one or more users to the group. - * - * @param session The session of the user. - * @param groupId The id of the group to add users into. - * @param ids The ids of the users to add or invite to the group. - */ - virtual std::future addGroupUsersAsync( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids - ) = 0; - - /** - * List all users part of the group. - * - * @param session The session of the user. - * @param groupId The id of the group. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual std::future listGroupUsersAsync( - NSessionPtr session, - const std::string& groupId, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "" - ) = 0; - - /** - * Kick one or more users from the group. - * - * @param session The session of the user. - * @param groupId The id of the group. - * @param ids The ids of the users to kick. - */ - virtual std::future kickGroupUsersAsync( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids - ) = 0; - - /** - * Join a group if it has open membership or request to join it. - * - * @param session The session of the user. - * @param groupId The id of the group to join. - */ - virtual std::future joinGroupAsync( - NSessionPtr session, - const std::string& groupId - ) = 0; - - /** - * Leave a group by id. - * - * @param session The session of the user. - * @param groupId The id of the group to leave. - */ - virtual std::future leaveGroupAsync( - NSessionPtr session, - const std::string& groupId - ) = 0; - - /** - * List groups on the server. - * - * @param session The session of the user. - * @param name The name filter to apply to the group list. - * @param limit The number of groups to list. - * @param cursor A cursor for the current position in the groups to list. - */ - virtual std::future listGroupsAsync( - NSessionPtr session, - const std::string& name, - int32_t limit = 0, - const std::string& cursor = "" - ) = 0; - - /** - * List of groups the current user is a member of. - * - * @param session The session of the user. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual std::future listUserGroupsAsync( - NSessionPtr session, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "" - ) = 0; - - /** - * List groups a user is a member of. - * - * @param session The session of the user. - * @param userId The id of the user whose groups to list. - * @param limit The max number of records to return. Between 1 and 100. - * @param state The group membership state to list. - * @param cursor An optional next page cursor. - */ - virtual std::future listUserGroupsAsync( - NSessionPtr session, - const std::string& userId, - const opt::optional& limit, - const opt::optional& state, - const std::string& cursor = "" - ) = 0; - - /** - * Promote a set of users in a group to the next role up. - * - * @param session The session of the user. - * @param groupId The group ID to promote in. - * @param ids The ids of the users to promote. - */ - virtual std::future promoteGroupUsersAsync( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids - ) = 0; - - /** - * Demote a set of users in a group to the next role down. - * - * @param session The session of the user. - * @param groupId The group ID to demote in. - * @param ids The ids of the users to demote. - */ - virtual std::future demoteGroupUsersAsync( - NSessionPtr session, - const std::string& groupId, - const std::vector& ids - ) = 0; - - /** - * Update a group. - * - * The user must have the correct access permissions for the group. - * - * @param session The session of the user. - * @param groupId The id of the group to update. - * @param name A new name for the group. - * @param description A new description for the group. - * @param avatarUrl A new avatar url for the group. - * @param langTag A new language tag in BCP-47 format for the group. - * @param open True if the group should have open membership. - */ - virtual std::future updateGroupAsync( - NSessionPtr session, - const std::string& groupId, - const opt::optional& name = opt::nullopt, - const opt::optional& description = opt::nullopt, - const opt::optional& avatarUrl = opt::nullopt, - const opt::optional& langTag = opt::nullopt, - const opt::optional& open = opt::nullopt - ) = 0; - - /** - * List records from a leaderboard. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard to list. - * @param ownerIds Record owners to fetch with the list of records. - * @param limit The number of records to list. - * @param cursor A cursor for the current position in the leaderboard records to list. - */ - virtual std::future listLeaderboardRecordsAsync( - NSessionPtr session, - const std::string& leaderboardId, - const std::vector& ownerIds = {}, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt - ) = 0; - - /** - * List leaderboard records from a given leaderboard around the owner. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard to list. - * @param ownerId The owner to retrieve records around. - * @param limit Max number of records to return. Between 1 and 100. - */ - virtual std::future listLeaderboardRecordsAroundOwnerAsync( - NSessionPtr session, - const std::string& leaderboardId, - const std::string& ownerId, - const opt::optional& limit = opt::nullopt - ) = 0; - - /** - * Write a record to a leaderboard. - * - * @param session The session for the user. - * @param leaderboardId The id of the leaderboard to write. - * @param score The score for the leaderboard record. - * @param subscore The subscore for the leaderboard record. - * @param metadata The metadata for the leaderboard record. - */ - virtual std::future writeLeaderboardRecordAsync( - NSessionPtr session, - const std::string& leaderboardId, - std::int64_t score, - const opt::optional& subscore = opt::nullopt, - const opt::optional& metadata = opt::nullopt - ) = 0; - - /** - * A request to submit a score to a tournament. - * - * @param session The session for the user. - * @param tournamentId The tournament ID to write the record for. - * @param score The score value to submit. - * @param subscore An optional secondary value. - * @param metadata A JSON object of additional properties. - */ - virtual std::future writeTournamentRecordAsync( - NSessionPtr session, - const std::string& tournamentId, - std::int64_t score, - const opt::optional& subscore = opt::nullopt, - const opt::optional& metadata = opt::nullopt - ) = 0; - - /** - * Delete a leaderboard record. - * - * @param session The session of the user. - * @param leaderboardId The id of the leaderboard with the record to be deleted. - */ - virtual std::future deleteLeaderboardRecordAsync( - NSessionPtr session, - const std::string& leaderboardId - ) = 0; - - /** - * Fetch a list of matches active on the server. - * - * @param session The session of the user. - * @param min_size The minimum number of match participants. - * @param max_size The maximum number of match participants. - * @param limit The number of matches to list. - * @param label The label to filter the match list on. - * @param authoritative true to include authoritative matches. - */ - virtual std::future listMatchesAsync( - NSessionPtr session, - const opt::optional& min_size = opt::nullopt, - const opt::optional& max_size = opt::nullopt, - const opt::optional& limit = opt::nullopt, - const opt::optional& label = opt::nullopt, - const opt::optional& query = opt::nullopt, - const opt::optional& authoritative = opt::nullopt - ) = 0; - - /** - * List notifications for the user with an optional cursor. - * - * @param session The session of the user. - * @param limit The number of notifications to list. - * @param cacheableCursor A cursor for the current position in notifications to list. - */ - virtual std::future listNotificationsAsync( - NSessionPtr session, - const opt::optional& limit = opt::nullopt, - const opt::optional& cacheableCursor = opt::nullopt - ) = 0; - - /** - * Delete one or more notifications by id. - * - * @param session The session of the user. - * @param notificationIds The notification ids to remove. - */ - virtual std::future deleteNotificationsAsync( - NSessionPtr session, - const std::vector& notificationIds - ) = 0; - - /** - * List messages from a chat channel. - * - * @param session The session of the user. - * @param channelId A channel identifier. - * @param limit The number of chat messages to list. - * @param cursor A cursor for the current position in the messages history to list. - * @param forward Fetch messages forward from the current cursor (or the start). - */ - virtual std::future listChannelMessagesAsync( - NSessionPtr session, - const std::string& channelId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - const opt::optional& forward = opt::nullopt - ) = 0; - - /** - * List active/upcoming tournaments based on given filters. - * - * @param session The session of the user. - * @param categoryStart The start of the categories to include. Defaults to 0. - * @param categoryEnd The end of the categories to include. Defaults to 128. - * @param startTime The start time for tournaments. Defaults to current Unix time. - * @param endTime The end time for tournaments. Defaults to +1 year from current Unix time. - * @param limit Max number of records to return. Between 1 and 100. - * @param cursor A next page cursor for listings. - */ - virtual std::future listTournamentsAsync( - NSessionPtr session, - const opt::optional& categoryStart = opt::nullopt, - const opt::optional& categoryEnd = opt::nullopt, - const opt::optional& startTime = opt::nullopt, - const opt::optional& endTime = opt::nullopt, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt - ) = 0; - - /** - * List tournament records from a given tournament. - * - * @param session The session of the user. - * @param tournamentId The ID of the tournament to list for. - * @param limit Max number of records to return. Between 1 and 100. - * @param cursor A next or previous page cursor. - * @param ownerIds One or more owners to retrieve records for. - */ - virtual std::future listTournamentRecordsAsync( - NSessionPtr session, - const std::string& tournamentId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt, - const std::vector& ownerIds = {} - ) = 0; - - /** - * List tournament records from a given tournament around the owner. - * - * @param session The session of the user. - * @param tournamentId The ID of the tournament to list for. - * @param ownerId The owner to retrieve records around. - * @param limit Max number of records to return. Between 1 and 100. - */ - virtual std::future listTournamentRecordsAroundOwnerAsync( - NSessionPtr session, - const std::string& tournamentId, - const std::string& ownerId, - const opt::optional& limit = opt::nullopt - ) = 0; - - /** - * Join a tournament if it has open membership or request to join it. - * - * @param session The session of the user. - * @param tournamentId The id of the tournament to join. - */ - virtual std::future joinTournamentAsync( - NSessionPtr session, - const std::string& tournamentId - ) = 0; - - /** - * List storage objects in a collection which have public read access. - * - * @param session The session of the user. - * @param collection The collection to list over. - * @param limit The number of objects to list. - * @param cursor A cursor to paginate over the collection. - */ - virtual std::future listStorageObjectsAsync( - NSessionPtr session, - const std::string& collection, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt - ) = 0; - - /** - * List storage objects in a collection which belong to a specific user and have public read access. - * - * @param session The session of the user. - * @param collection The collection to list over. - * @param userId The user ID of the user to list objects for. - * @param limit The number of objects to list. - * @param cursor A cursor to paginate over the collection. - */ - virtual std::future listUsersStorageObjectsAsync( - NSessionPtr session, - const std::string& collection, - const std::string& userId, - const opt::optional& limit = opt::nullopt, - const opt::optional& cursor = opt::nullopt - ) = 0; - - /** - * Write objects to the storage engine. - * - * @param session The session of the user. - * @param objects The objects to write. - */ - virtual std::future writeStorageObjectsAsync( - NSessionPtr session, - const std::vector& objects - ) = 0; - - /** - * Read one or more objects from the storage engine. - * - * @param session The session of the user. - * @param objectIds The objects to read. - */ - virtual std::future readStorageObjectsAsync( - NSessionPtr session, - const std::vector& objectIds - ) = 0; - - /** - * Delete one or more storage objects. - * - * @param session The session of the user. - * @param objectIds The ids of the objects to delete. - */ - virtual std::future deleteStorageObjectsAsync( - NSessionPtr session, - const std::vector& objectIds - ) = 0; - - /** - * Execute a server framework function with an input payload on the server. - * - * @param session The session of the user. - * @param id The id of the function to execute on the server. - * @param payload The payload to send with the function call. - */ - virtual std::future rpcAsync( - NSessionPtr session, - const std::string& id, - const opt::optional& payload = opt::nullopt - ) = 0; - - /** - * Execute an RPC function with an input payload on the server. - * - * @param http_key The server's runtime HTTP key. - * @param id The id of the function to execute on the server. - * @param payload The payload to send with the function call. - */ - virtual std::future rpcAsync( - const std::string& http_key, - const std::string& id, - const opt::optional& payload = opt::nullopt - ) = 0; - }; - - using NClientPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NError.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NError.h deleted file mode 100644 index 5f85dcaa4..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NError.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Error codes - enum class ErrorCode - { - /// Unknown error - Unknown = 0, - - /// Some requested entity was not found. - NotFound = 1, - - /// Some entity that we attempted to create already exists. - AlreadyExists = 2, - - /// Client specified an invalid argument. - InvalidArgument = 3, - - /// The request does not have valid authentication credentials for the operation. - Unauthenticated = 4, - - /// The caller does not have permission to execute the specified operation. - PermissionDenied = 5, - - /// The service is currently unavailable. This is a most likely a transient - /// condition and may be corrected by retrying with a backoff. - /// - /// \warning Although data MIGHT not have been transmitted when this - /// status occurs, there is NOT A GUARANTEE that the server has not seen - /// anything. So in general it is unsafe to retry on this status code - /// if the call is non-idempotent. - ConnectionError = -1, - - /// Internal errors. Means some invariants expected by underlying System has - /// been broken. If you see one of these errors, Something is very broken. - InternalError = -2, - - /// The request has been cancelled by user. - CancelledByUser = -3 - }; - - struct NError - { - NError() {} - explicit NError(const std::string& message, ErrorCode code = ErrorCode::Unknown) : - message(message), code(code) {} - explicit NError(std::string&& message, ErrorCode code = ErrorCode::Unknown) : - message(std::move(message)), code(code) {} - - std::string message; - ErrorCode code = ErrorCode::Unknown; - }; - - NAKAMA_API const char* toString(ErrorCode code); - NAKAMA_API std::string toString(const NError& error); - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NException.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NException.h deleted file mode 100644 index 8ed3c1802..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NException.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "NError.h" - -namespace Nakama -{ - class NException : public std::runtime_error { - public: - NException(const NError& error) - : std::runtime_error(error.message), error(error) {} - - const NError error; - }; -} diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NExport.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NExport.h deleted file mode 100644 index 35908c9c5..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NExport.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#undef NAKAMA_API - -#if defined _WIN32 || defined __CYGWIN__ || defined FORCE_DLL_IMPORT_EXPORT - #ifdef NAKAMA_SHARED_LIBRARY_EXPORTS - #define NAKAMA_API __declspec(dllexport) - #else - #define NAKAMA_API __declspec(dllimport) - #endif -#elif __GNUC__ >= 4 - #ifdef NAKAMA_SHARED_LIBRARY_EXPORTS - #define NAKAMA_API __attribute__((visibility("default"))) - #else - #define NAKAMA_API - #endif -#else - #define NAKAMA_API -#endif diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NHttpTransportInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NHttpTransportInterface.h deleted file mode 100644 index bcb9786ad..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NHttpTransportInterface.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - using NHttpHeaders = std::map; - using NHttpQueryArgs = std::multimap; - - enum class NHttpReqMethod - { - GET, - POST, - PUT, - DEL, - }; - - struct NHttpRequest - { - NHttpReqMethod method = NHttpReqMethod::POST; - NHttpHeaders headers; - NHttpQueryArgs queryArgs; - std::string path; - std::string body; - }; - - struct NHttpResponse - { - int statusCode = 0; /// HTTP status code, 200 - OK - std::string body; /// response body - std::string errorMessage; /// error message string, intended for use if a local failure (i.e., no error body returned from server) - }; - - using NHttpResponsePtr = std::shared_ptr; - using NHttpResponseCallback = std::function; - - namespace InternalStatusCodes - { - static const int CONNECTION_ERROR = 600; /// this indicates a general connection error - static const int NOT_INITIALIZED_ERROR = 601; /// HTTP client is not initialized properly - static const int CANCELLED_BY_USER = 602; /// cancelled by user - static const int INTERNAL_TRANSPORT_ERROR = 603; /// Errors in HTTP Transport - } - - /** - * HTTP transport interface - */ - class NHttpTransportInterface - { - public: - virtual ~NHttpTransportInterface() {} - - virtual void setBaseUri(const std::string& uri) = 0; - - virtual void tick() = 0; - - /** - * Invoke HTTP request - */ - virtual void request(const NHttpRequest& req, const NHttpResponseCallback& callback = nullptr) = 0; - - /** - * Cancel all requests - * - * Note: this doesn't guarantee server will not receive or not received - * any currently pending request - */ - virtual void cancelAllRequests() = 0; - }; - - using NHttpTransportPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NSessionInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NSessionInterface.h deleted file mode 100644 index ef69705c4..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NSessionInterface.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2022 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - class NAKAMA_API NSessionInterface - { - public: - virtual ~NSessionInterface() {} - - /** - * @return The authentication token used to construct this session. - */ - virtual const std::string& getAuthToken() const = 0; - - /** - * @return The refresh token used to construct this session. - */ - virtual const std::string& getRefreshToken() const = 0; - - /** - * @return True if the user account for this session was just created. - */ - virtual bool isCreated() const = 0; - - /** - * @return The username of the user who owns this session. - */ - virtual const std::string& getUsername() const = 0; - - /** - * @return The ID of the user who owns this session. - */ - virtual const std::string& getUserId() const = 0; - - /** - * @return The timestamp in milliseconds when this session object was created. - */ - virtual NTimestamp getCreateTime() const = 0; - - /** - * @return The timestamp in milliseconds when this session will expire. - */ - virtual NTimestamp getExpireTime() const = 0; - - /** - * @return True if the session has expired against the current time. - */ - virtual bool isExpired() const = 0; - - /** - * Check if the session's token has expired against the input time. - * - * @param now The time to compare against the session. - * Use getUnixTimestampMs() to get current time. - * @return true if the session has expired. - */ - virtual bool isExpired(NTimestamp now) const = 0; - - /** - * @return True if the session has expired against the current time. - */ - virtual bool isRefreshExpired() const = 0; - - /** - * Check if the session's refresh token has expired against the input time. - * - * @param now The time to compare against the session. - * Use getUnixTimestampMs() to get current time. - * @return true if the session has expired. - */ - virtual bool isRefreshExpired(NTimestamp now) const = 0; - - /** - * Get session variables. - * - * @return NStringMap. - */ - virtual const NStringMap& getVariables() const = 0; - - /** - * Get session variable value by name. - * - * @return variable value. - */ - virtual std::string getVariable(const std::string& name) const = 0; - }; - - using NSessionPtr = std::shared_ptr; - - /** - * Restore a session from an authentication token. - * - * @param token The authentication token from a NSessionInterface. - * @return A session restored from the authentication token. - */ - NAKAMA_API NSessionPtr restoreSession(const std::string& token, const std::string& refreshToken); - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NTypes.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NTypes.h deleted file mode 100644 index 2c8c8d558..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NTypes.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#ifndef NAKAMA_NAMESPACE - #define NAKAMA_NAMESPACE Nakama - #define NAKAMA_NAMESPACE_BEGIN namespace NAKAMA_NAMESPACE { - #define NAKAMA_NAMESPACE_END } -#endif - -NAKAMA_NAMESPACE_BEGIN - - /// The group role status. - enum class NUserGroupState - { - SUPERADMIN = 0, ///< The user is a superadmin with full control of the group. - ADMIN = 1, ///< The user is an admin with additional privileges. - MEMBER = 2, ///< The user is a regular member. - JOIN_REQUEST = 3 ///< The user has requested to join the group - }; - - /// The available channel types on the server. - enum class NChannelType - { - TYPE_UNSPECIFIED = 0, ///< Default case. Assumed as ROOM type. - ROOM = 1, ///< A chat room which can be created dynamically with a name. - DIRECT_MESSAGE = 2, ///< A private chat between two users. - GROUP = 3 ///< A chat within a group on the server. - }; - - /// UNIX time in milliseconds. - /// Use getUnixTimestampMs() to get current time. - using NTimestamp = uint64_t; - - /// array of bytes - using NBytes = std::string; - - using NStringMap = std::map; - using NStringDoubleMap = std::map; - namespace opt = nonstd; - - /// Constant for defaut port. - /// This is not valid port, actual port will be selected automatically. - static const int32_t DEFAULT_PORT = -1; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NUtils.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/NUtils.h deleted file mode 100644 index 04b1dfcaf..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NUtils.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /** - * Get current UNIX time in milliseconds. - * - * Returns number of milliseconds that have elapsed since 00:00:00 Thursday, 1 January 1970. - * - * @return UNIX time in milliseconds. - */ - NAKAMA_API NTimestamp getUnixTimestampMs(); - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/Nakama.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/Nakama.h deleted file mode 100644 index 0a9d4bb44..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/Nakama.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/URLParts.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/URLParts.h deleted file mode 100644 index f12a640e1..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/URLParts.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -namespace Nakama { - struct URLParts { - std::string scheme; - std::string host; - opt::optional port; - std::string pathAndArgs; - std::string url; - }; -} diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/config.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/config.h deleted file mode 100644 index 5709a2196..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/config.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#define optional_CONFIG_SELECT_OPTIONAL optional_OPTIONAL_NONSTD -#define WITH_EXTERNAL_HTTP -#define WITH_EXTERNAL_WS -#define WITH_EXTERNAL_WS_IO diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccount.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccount.h deleted file mode 100644 index fd39f6cae..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccount.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A user with additional account details. Always the current user. - struct NAccount { - NUser user; ///< The user object. - std::string wallet; ///< The user's wallet data. - std::string email; ///< The email address of the user. - std::vector devices; ///< The devices which belong to the user's account. - std::string customId; ///< The custom id in the user's account. - NTimestamp verifyTime = 0; ///< The UNIX time when the user's email was verified. - NTimestamp disableTime = 0; ///< The UNIX time when the user's account was disabled/banned. - }; - - using NAccountPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccountDevice.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccountDevice.h deleted file mode 100644 index 71d3a70a6..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NAccountDevice.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Used with authenticate/link/unlink and user. - struct NAccountDevice { - std::string id; ///< A device identifier. Should be obtained by a platform-specific device API. - NStringMap vars; ///< Extra information that will be bundled in the session token. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessage.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessage.h deleted file mode 100644 index 29e9b62bc..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessage.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A message sent on a channel. - struct NChannelMessage - { - std::string channelId; ///< The channel this message belongs to. - std::string messageId; ///< The unique ID of this message. - int32_t code = 0; ///< The code representing a message type or category. - std::string senderId; ///< Message sender, usually a user ID. - std::string username; ///< The username of the message sender, if any. - std::string content; ///< The content payload. - NTimestamp createTime = 0; ///< The UNIX time when the message was created. - NTimestamp updateTime = 0; ///< The UNIX time when the message was last updated. - bool persistent = false; ///< True if the message was persisted to the channel's history, false otherwise. - std::string roomName; ///< The name of the chat room, or an empty string if this message was not sent through a chat room. - std::string groupId; ///< The ID of the group, or an empty string if this message was not sent through a group channel. - std::string userIdOne; ///< The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - std::string userIdTwo; ///< The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessageList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessageList.h deleted file mode 100644 index 171ff5f44..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NChannelMessageList.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A list of channel messages, usually a result of a list operation. - struct NChannelMessageList - { - std::vector messages; ///< A list of messages. - std::string nextCursor; ///< The cursor to send when retireving the next page, if any. - std::string prevCursor; ///< The cursor to send when retrieving the previous page, if any. - }; - - using NChannelMessageListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriend.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriend.h deleted file mode 100644 index 6817328eb..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriend.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A friend of a user. - struct NFriend - { - /// The friendship status. - enum class State { - FRIEND = 0, ///< The user is a friend of the current user. - INVITE_SENT = 1, ///< The current user has sent an invite to the user. - INVITE_RECEIVED = 2, ///< The current user has received an invite from this user. - BLOCKED = 3 ///< The current user has blocked this user. - }; - - NUser user; ///< The user object. - State state; ///< The friend status. - NTimestamp updateTime = 0; ///< Time of the latest relationship update. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriendList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriendList.h deleted file mode 100644 index a55bbffad..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NFriendList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - ///< A collection of zero or more friends of the user. - struct NFriendList - { - std::vector friends; ///< The Friend objects. - std::string cursor; ///< Cursor for the next page of results, if any. - }; - - using NFriendListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroup.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroup.h deleted file mode 100644 index fc54507c0..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroup.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A group in the server. - struct NGroup - { - std::string id; ///< The id of a group. - std::string creatorId; ///< The id of the user who created the group. - std::string name; ///< The unique name of the group. - std::string description; ///< A description for the group. - std::string lang; ///< The language expected to be a tag which follows the BCP-47 spec. - std::string metadata; ///< Additional information stored as a JSON object. - std::string avatarUrl; ///< A URL for an avatar image. - bool open = false; ///< Anyone can join open groups, otherwise only admins can accept members. - int32_t edgeCount = 0; ///< The current count of all members in the group. - int32_t maxCount = 0; ///< The maximum number of members allowed. - NTimestamp createTime = 0; ///< The UNIX time when the group was created. - NTimestamp updateTime = 0; ///< The UNIX time when the group was last updated. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupList.h deleted file mode 100644 index cc45bb012..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// One or more groups returned from a listing operation. - struct NGroupList - { - std::vector groups; ///< One or more groups. - std::string cursor; ///< A cursor used to get the next page. - }; - - using NGroupListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUser.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUser.h deleted file mode 100644 index 2505c0d22..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUser.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A single user-role pair. - struct NGroupUser - { - NUser user; ///< User. - NUserGroupState state; ///< Their relationship to the group. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUserList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUserList.h deleted file mode 100644 index 5a23ef54f..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NGroupUserList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A list of users belonging to a group, along with their role. - struct NGroupUserList - { - std::vector groupUsers; ///< User-role pairs for a group. - std::string cursor; ///< Cursor for the next page of results, if any. - }; - - using NGroupUserListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecord.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecord.h deleted file mode 100644 index c55d6d788..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecord.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Represents a complete leaderboard record with all scores and associated metadata. - struct NLeaderboardRecord - { - std::string leaderboardId; ///< The ID of the leaderboard this score belongs to. - std::string ownerId; ///< The ID of the score owner, usually a user or group. - std::string username; ///< The username of the score owner, if the owner is a user. - std::int64_t score = 0; ///< The score value. - std::int64_t subscore = 0; ///< An optional subscore value. - int32_t numScore = 0; ///< The number of submissions to this score record. - uint32_t maxNumScore = 0; ///< The maximum number of score updates allowed by the owner. - std::string metadata; ///< Metadata. - NTimestamp createTime = 0; ///< The UNIX time when the leaderboard record was created. - NTimestamp updateTime = 0; ///< The UNIX time when the leaderboard record was updated. - NTimestamp expiryTime = 0; ///< The UNIX time when the leaderboard record expires. - std::int64_t rank = 0; ///< The rank of this record. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecordList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecordList.h deleted file mode 100644 index dcfb53788..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NLeaderboardRecordList.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A set of leaderboard records, may be part of a leaderboard records page or a batch of individual records. - struct NLeaderboardRecordList - { - std::vector records; ///< A list of leaderboard records. - std::vector ownerRecords; ///< A batched set of leaderboard records belonging to specified owners. - std::string nextCursor; ///< The cursor to send when retrieving the next page, if any. - std::string prevCursor; ///< The cursor to send when retrieving the previous page, if any. - }; - - using NLeaderboardRecordListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatch.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatch.h deleted file mode 100644 index 0d4b7bd6c..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatch.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Represents a realtime match. - struct NMatch - { - std::string matchId; ///< The ID of the match, can be used to join. - bool authoritative = false; ///< True if it's an server-managed authoritative match, false otherwise. - std::string label; ///< Match label, if any. - int32_t size = 0; ///< Current number of users in the match. - std::vector presences; ///< The users currently in the match. - NUserPresence self; ///< A reference to the current user's presence in the match. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatchList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatchList.h deleted file mode 100644 index de628ac59..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NMatchList.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A list of realtime matches. - struct NMatchList - { - std::vector matches; ///< A number of matches corresponding to a list operation. - }; - - using NMatchListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotification.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotification.h deleted file mode 100644 index e245566fe..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotification.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A notification in the server. - struct NNotification - { - std::string id; ///< ID of the Notification. - std::string subject; ///< Subject of the notification. - std::string content; ///< Content of the notification in JSON. - int32_t code = 0; ///< Category code for this notification. - std::string senderId; ///< ID of the sender, if a user. Otherwise 'null'. - NTimestamp createTime = 0; ///< The UNIX time when the notification was created. - bool persistent = false; ///< True if this notification was persisted to the database. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotificationList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotificationList.h deleted file mode 100644 index ef4177712..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NNotificationList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A collection of zero or more notifications. - struct NNotificationList - { - std::vector notifications; ///< Collection of notifications. - std::string cacheableCursor; ///< Use this cursor to paginate notifications. Cache this to catch up to new notifications. - }; - - using NNotificationListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NRpc.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NRpc.h deleted file mode 100644 index e0f6aadbf..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NRpc.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Execute an Lua function on the server. - struct NRpc - { - std::string id; ///< The identifier of the function. - std::string payload; ///< The payload of the function which must be a JSON object. - std::string httpKey; ///< The authentication key used when executed as a non-client HTTP request. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObject.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObject.h deleted file mode 100644 index d175f5934..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObject.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// An object within the storage engine. - struct NStorageObject - { - std::string collection; ///< The collection which stores the object. - std::string key; ///< The key of the object within the collection. - std::string userId; ///< The user owner of the object. - std::string value; ///< The value of the object. - std::string version; ///< The version hash of the object. - NStoragePermissionRead permissionRead - = NStoragePermissionRead::NO_READ; ///< The read access permissions for the object. - NStoragePermissionWrite permissionWrite - = NStoragePermissionWrite::NO_WRITE; ///< The write access permissions for the object. - NTimestamp createTime = 0; ///< The UNIX time when the object was created. - NTimestamp updateTime = 0; ///< The UNIX time when the object was last updated. - }; - - using NStorageObjects = std::vector; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectAck.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectAck.h deleted file mode 100644 index c50da266b..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectAck.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A storage acknowledgement. - struct NStorageObjectAck - { - std::string collection; ///< The collection which stores the object. - std::string key; ///< The key of the object within the collection. - std::string version; ///< The version hash of the object. - std::string userId; ///< The owner of the object. - }; - - /// Batch of acknowledgements. - using NStorageObjectAcks = std::vector; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectId.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectId.h deleted file mode 100644 index 6b196f699..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectId.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Storage objects to get. - struct NReadStorageObjectId - { - std::string collection; ///< The collection which stores the object. - std::string key; ///< The key of the object within the collection. - std::string userId; ///< The user owner of the object. - }; - - /// Storage objects to delete. - struct NDeleteStorageObjectId - { - std::string collection; ///< The collection which stores the object. - std::string key; ///< The key of the object within the collection. - std::string version; ///< The version hash of the object. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectList.h deleted file mode 100644 index 5320aa182..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// List of storage objects. - struct NStorageObjectList - { - std::vector objects; ///< The list of storage objects. - std::string cursor; ///< The cursor for the next page of results, if any. - }; - - using NStorageObjectListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectWrite.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectWrite.h deleted file mode 100644 index bbc456c24..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStorageObjectWrite.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// The object to store. - struct NStorageObjectWrite - { - std::string collection; ///< The collection which stores the object. - std::string key; ///< The key of the object within the collection. - std::string value; ///< The value of the object. Must be JSON - std::string version; ///< The version hash of the object to check. Possible values are: ["", "*", "#hash#"]. - opt::optional permissionRead; ///< The read access permissions for the object. - opt::optional permissionWrite; ///< The write access permissions for the object. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStoragePermissions.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStoragePermissions.h deleted file mode 100644 index 7319f0955..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NStoragePermissions.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// The read access permissions. - enum class NStoragePermissionRead - { - NO_READ = 0, ///< The object is only readable by server runtime. - OWNER_READ = 1, ///< Only the user who owns it may access. - PUBLIC_READ = 2 ///< Any user can read the object. - }; - - /// The write access permissions. - enum class NStoragePermissionWrite - { - NO_WRITE = 0, ///< The object is only writable by server runtime. - OWNER_WRITE = 1 ///< Only the user who owns it may write. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournament.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournament.h deleted file mode 100644 index e8d2dd715..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournament.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A tournament on the server. - struct NTournament - { - std::string id; ///< The ID of the tournament. - std::string title; ///< The title for the tournament. - std::string description; ///< The description of the tournament. May be blank. - uint32_t category = 0; ///< The category of the tournament. e.g. "vip" could be category 1. - uint32_t sortOrder = 0; ///< ASC or DESC sort mode of scores in the tournament. - uint32_t size = 0; ///< The current number of players in the tournament. - uint32_t maxSize = 0; ///< The maximum number of players for the tournament. - uint32_t maxNumScore = 0; ///< The maximum score updates allowed per player for the current tournament. - bool canEnter = false; ///< True if the tournament is active and can enter. A computed value. - NTimestamp createTime = 0; ///< The UNIX time when the tournament was created. - NTimestamp startTime = 0; ///< The UNIX time when the tournament will start. - NTimestamp endTime = 0; ///< The UNIX time when the tournament will be stopped. - uint32_t endActive = 0; ///< The UNIX time when the tournament stops being active until next reset. A computed value. - uint32_t nextReset = 0; ///< The UNIX time when the tournament is next playable. A computed value. - uint32_t duration = 0; ///< Duration of the tournament in seconds. - uint32_t startActive = 0; ///< The UNIX time when the tournament start being active. A computed value. - std::string metadata; ///< Additional information stored as a JSON object. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentList.h deleted file mode 100644 index a89ca9889..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A list of tournaments. - struct NTournamentList - { - std::vector tournaments; ///< The list of tournaments returned. - std::string cursor; ///< A pagination cursor (optional). - }; - - using NTournamentListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentRecordList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentRecordList.h deleted file mode 100644 index 80a321693..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NTournamentRecordList.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A set of tournament records which may be part of a tournament records page or a batch of individual records. - struct NTournamentRecordList - { - std::vector records; ///< A list of tournament records. - std::vector ownerRecords; ///< A batched set of tournament records belonging to specified owners. - std::string nextCursor; ///< The cursor to send when retireving the next page, if any. - std::string prevCursor; ///< The cursor to send when retrieving the previous page, if any. - }; - - using NTournamentRecordListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUser.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUser.h deleted file mode 100644 index bdf816d31..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUser.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A user in the server. - struct NUser - { - std::string id; ///< The id of the user's account. - std::string username; ///< The username of the user's account. - std::string displayName; ///< The display name of the user. - std::string avatarUrl; ///< A URL for an avatar image. - std::string lang; ///< The language expected to be a tag which follows the BCP-47 spec. - std::string location; ///< The location set by the user. - std::string timeZone; ///< The timezone set by the user. - std::string metadata; ///< Additional information stored as a JSON object. - std::string facebookId; ///< The Facebook id in the user's account. - std::string googleId; ///< The Google id in the user's account. - std::string gameCenterId; ///< The Apple Game Center in of the user's account. - std::string appleId; ///< The Apple Sign In ID in the user's account. - std::string steamId; ///< The Steam id in the user's account. - bool online = false; ///< Indicates whether the user is currently online. - int32_t edgeCount = 0; ///< Number of related edges to this user (friends). - NTimestamp createdAt = 0; ///< The UNIX time when the user was created. - NTimestamp updatedAt = 0; ///< The UNIX time when the user was last updated. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroup.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroup.h deleted file mode 100644 index 5573dc1a8..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroup.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A single group-role pair. - struct NUserGroup - { - NGroup group; ///< Group. - NUserGroupState state; ///< The user's relationship to the group. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroupList.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroupList.h deleted file mode 100644 index 4f3464bcc..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUserGroupList.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A list of groups belonging to a user, along with the user's role in each group. - struct NUserGroupList - { - std::vector userGroups; ///< Group-role pairs for a user. - std::string cursor; ///< Cursor for the next page of results, if any. - }; - - using NUserGroupListPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUsers.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUsers.h deleted file mode 100644 index 190a89110..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/data/NUsers.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A collection of zero or more users. - struct NUsers - { - std::vector users; - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NConsoleLogSink.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NConsoleLogSink.h deleted file mode 100644 index 7e63de12f..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NConsoleLogSink.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Writes logs to console output - class NAKAMA_API NConsoleLogSink : public NLogSinkInterface - { - public: - ~NConsoleLogSink() {} - - void log(NLogLevel level, const std::string& message, const char* func) override; - - void flush() override; - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogSinkInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogSinkInterface.h deleted file mode 100644 index 52c1f81d8..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogSinkInterface.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - enum class NLogLevel - { - Debug = 1, - Info = 2, - Warn = 3, - Error = 4, - Fatal = 5 - }; - - class NAKAMA_API NLogSinkInterface - { - public: - NLogSinkInterface() {} - virtual ~NLogSinkInterface() {} - - /** - * Output log message - * - * @param level the level of log message - * @param message the log message string - * @param func the function name from which log message comes. - * Usually has class name e.g. `NDefaultClient::onResponse` - */ - virtual void log(NLogLevel level, const std::string& message, const char* func = nullptr) = 0; - - /** - * Flush cached data. - */ - virtual void flush() = 0; - - /** - * Set the logging level boundary - * - * @param level the logging level boundary - */ - void setLevel(NLogLevel level) { _level = level; } - - /** - * Get the logging level boundary - * - * @return NLogLevel - */ - NLogLevel getLevel() const { return _level; } - - protected: - NLogLevel _level = NLogLevel::Info; - }; - - using NLogSinkPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogger.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogger.h deleted file mode 100644 index 828fd81e1..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/log/NLogger.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Ideas from: https://github.com/gabime/spdlog - -#pragma once - -#include -#include -#include -#include -#include -#include - -#ifndef NMODULE_NAME - #define NMODULE_NAME "" -#endif // !NMODULE_NAME - -#ifdef NLOGS_ENABLED - #define NLOG_DEBUG(msg) ::Nakama::NLogger::Debug (msg, NMODULE_NAME, __func__) - #define NLOG_INFO(msg) ::Nakama::NLogger::Info (msg, NMODULE_NAME, __func__) - #define NLOG_WARN(msg) ::Nakama::NLogger::Warn (msg, NMODULE_NAME, __func__) - #define NLOG_ERROR(msg) ::Nakama::NLogger::Error (msg, NMODULE_NAME, __func__) - #define NLOG_FATAL(msg) ::Nakama::NLogger::Fatal (msg, NMODULE_NAME, __func__) - #define NLOG(level, format,...) ::Nakama::NLogger::Format(level, NMODULE_NAME, __func__, format, ##__VA_ARGS__) -#else - #define NLOG_DEBUG(msg) do {} while (0) - #define NLOG_INFO(msg) do {} while (0) - #define NLOG_WARN(msg) do {} while (0) - #define NLOG_ERROR(msg) do {} while (0) - #define NLOG_FATAL(msg) do {} while (0) - #define NLOG(level, format,...) do {} while (0) -#endif // NLOGS_ENABLED - -NAKAMA_NAMESPACE_BEGIN - - /// Logger - class NAKAMA_API NLogger - { - public: - /** - * Initialize logger with NConsoleLogSink - * - * @param level logging boundary - */ - static void initWithConsoleSink(NLogLevel level = NLogLevel::Info); - - /** - * Initialize logger with custom log sink - * - * @param sink custom log sink - * @param level logging boundary - */ - static void init(NLogSinkPtr sink, NLogLevel level = NLogLevel::Info); - static NLogSinkPtr getSink(); - static void setSink(NLogSinkPtr sink); - static void setLevel(NLogLevel level); - - static void Debug(const std::string& message, const char* module_name, const char* func = nullptr); - static void Info (const std::string& message, const char* module_name, const char* func = nullptr); - static void Warn (const std::string& message, const char* module_name, const char* func = nullptr); - static void Error(const std::string& message, const char* module_name, const char* func = nullptr); - static void Fatal(const std::string& message, const char* module_name, const char* func = nullptr); - static void Log(NLogLevel level, const std::string& message, const char* module_name, const char* func = nullptr); - static void Format(NLogLevel level, const char* module_name, const char* func, const char* format, ...); - static void vFormat(NLogLevel level, const char* module_name, const char* func, const char* format, va_list args); - static void Error(const NError& error, const char* module_name, const char* func = nullptr); - static void Error(const NRtError& error, const char* module_name, const char* func = nullptr); - - private: - NLogger() = delete; - ~NLogger() = delete; - NLogger(const NLogger&) = delete; - void operator=(const NLogger&) = delete; - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientDisconnectInfo.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientDisconnectInfo.h deleted file mode 100644 index 273897656..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientDisconnectInfo.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - struct NRtClientDisconnectInfo - { - enum Code { - NORMAL_CLOSURE = 1000, - GOING_AWAY = 1001, - PROTOCOL_ERROR = 1002, - UNSUPPORTED_DATA = 1003, - NO_STATUS_RCVD = 1005, - ABNORMAL_CLOSURE = 1006, - INVALID_FRAME_PAYLOAD_DATA = 1007, - POLICY_VIOLATION = 1008, - MESSAGE_TOO_BIG = 1009, - MANDATORY_EXT = 1010, - INTERNAL_SERVER_ERROR = 1011, - TLS_HANDSHAKE = 1015, - - HEARTBEAT_FAILURE = 4000, - TRANSPORT_ERROR = 4001 - }; - - /// close code. - /// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent - uint16_t code = 0; - - /// close reason. Optional. - std::string reason; - - /// true if close was initiated by server. - bool remote = false; - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientInterface.h deleted file mode 100644 index 806ebbea3..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientInterface.h +++ /dev/null @@ -1,718 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - using RtErrorCallback = std::function; - - struct RtClientParameters - { - /// The host address of the server. Defaults to "127.0.0.1". - std::string host = "127.0.0.1"; - - /// The port number of the server. - /// Default is 7350 for non-SSL connection, 443 for SSL. - int32_t port = DEFAULT_PORT; - - /// Set connection strings to use the secure mode with the server. Defaults to false. - /// The server must be configured to make use of this option. With HTTP, GRPC, and WebSockets the server must - /// be configured with an SSL certificate or use a load balancer which performs SSL termination. - /// For rUDP you must configure the server to expose it's IP address so it can be bundled within session tokens. - /// See the server documentation for more information. - bool ssl = false; - - /// Platform specific parameters -#ifdef DEFAULT_PLATFORM_PARAMS - NPlatformParameters platformParams = {}; -#else - NPlatformParameters platformParams; -#endif - }; - - enum class NRtClientProtocol - { - /// Protobuf binary protocol. It is recommented to use for production - /// as it's faster and uses less traffic for communication. - /// Protobuf support is added in nakama server 2.3.0 - Protobuf, - - /// Json is text protocol. Might be useful for analyzing server traffic. - Json - }; - - /** - * A real-time client interface to interact with Nakama server. - */ - class NAKAMA_API NRtClientInterface - { - public: - virtual ~NRtClientInterface() {} - - /** - * Pumps requests queue in your thread. - * Call it periodically, each 50 ms is ok. - */ - virtual void tick() = 0; - - /** - * Get websocket transport which RtClient uses. - */ - virtual NRtTransportPtr getTransport() const = 0; - - /** - * Set events listener - * - * @param listener The listener of client events. - */ - virtual void setListener(NRtClientListenerInterface* listener) = 0; - - /** - * Set user data. - * - * Client just holds this data so you can receive it later when you need it. - * - * @param userData The user data. - */ - virtual void setUserData(void* userData) = 0; - - /** - * Get user data. - * - * @return The user data. - */ - virtual void* getUserData() const = 0; - - /** - * Set heartbeat interval in milliseconds. Disconnect event will be - * detected in at most 2 x interval. - * - * Default is 5 seconds. - * - * @param interval interval in ms send heartbeats in. Passing opt::nullopt disables heartbeats. - */ - virtual void setHeartbeatIntervalMs(opt::optional ms) = 0; - - /** - * Get heartbeat interval in milliseconds. - * - * @return heartbeat interval value or opt::nullopt if disabled - */ - virtual opt::optional getHeartbeatIntervalMs() = 0; - - /** - * Connect to the server. - * - * @param session The session of the user. - * @param createStatus True if the socket should show the user as online to others. - * @param protocol Communication protocol. Default is Protobuf. - */ - virtual void connect(NSessionPtr session, bool createStatus, NRtClientProtocol protocol = NRtClientProtocol::Protobuf) = 0; - - /** - * Connect to the server. - * - * @param session The session of the user. - * @param createStatus True if the socket should show the user as online to others. - * @param protocol Communication protocol. Default is Protobuf. - */ - virtual std::future connectAsync(NSessionPtr session, bool createStatus, NRtClientProtocol protocol = NRtClientProtocol::Protobuf) = 0; - - /** - * @return True if connected to server. - */ - virtual bool isConnected() const = 0; - - /** - * Close the connection with the server. - */ - virtual void disconnect() = 0; - - /** - * Close the connection with the server. - */ - virtual std::future disconnectAsync() = 0; - - /** - * Join a chat channel on the server. - * - * @param target The target channel to join. - * @param type The type of channel to join. - * @param persistence True if chat messages should be stored. - * @param hidden True if the user should be hidden on the channel. - */ - virtual void joinChat( - const std::string& target, - NChannelType type, - const opt::optional& persistence = opt::nullopt, - const opt::optional& hidden = opt::nullopt, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Leave a chat channel on the server. - * - * @param channelId The channel to leave. - */ - virtual void leaveChat( - const std::string& channelId, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Send a chat message to a channel on the server. - * - * @param channelId The channel to send on. - * @param content The content of the chat message. Must be a JSON object. - */ - virtual void writeChatMessage( - const std::string& channelId, - const std::string& content, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Update a chat message to a channel on the server. - * - * @param channelId The ID of the chat channel with the message. - * @param messageId The ID of the message to update. - * @param content The content update for the message. Must be a JSON object. - */ - virtual void updateChatMessage( - const std::string& channelId, - const std::string& messageId, - const std::string& content, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Remove a chat message from a channel on the server. - * - * @param channelId The chat channel with the message. - * @param messageId The ID of a chat message to remove. - */ - virtual void removeChatMessage( - const std::string& channelId, - const std::string& messageId, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Create a multiplayer match on the server. - */ - virtual void createMatch( - std::function successCallback, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Join a multiplayer match by ID. - * - * @param matchId A match ID. - */ - virtual void joinMatch( - const std::string& matchId, - const NStringMap& metadata, - std::function successCallback, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Join a multiplayer match with a matchmaker. - * - * @param token A matchmaker ticket result object. - */ - virtual void joinMatchByToken( - const std::string& token, - std::function successCallback, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Leave a match on the server. - * - * @param matchId The match to leave. - */ - virtual void leaveMatch( - const std::string& matchId, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Join the matchmaker pool and search for opponents on the server. - * - * @param minCount The minimum number of players to compete against. - * @param maxCount The maximum number of players to compete against. - * @param query A matchmaker query to search for opponents. - * @param stringProperties A set of k/v properties to provide in searches. - * @param numericProperties A set of k/v numeric properties to provide in searches. - * @param countMultiple An optional multiple of the matched count that must be satisfied. - */ - virtual void addMatchmaker( - const opt::optional& minCount = opt::nullopt, - const opt::optional& maxCount = opt::nullopt, - const opt::optional& query = opt::nullopt, - const NStringMap& stringProperties = {}, - const NStringDoubleMap& numericProperties = {}, - const opt::optional& countMultiple = opt::nullopt, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Leave the matchmaker pool by ticket. - * - * @param ticket The ticket returned by the matchmaker on join. See NMatchmakerTicket.ticket. - */ - virtual void removeMatchmaker( - const std::string& ticket, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Send a state change to a match on the server. - * - * When no presences are supplied the new match state will be sent to all presences. - * - * @param matchId The Id of the match. - * @param opCode An operation code for the match state. - * @param data The new state to send to the match. - * @param presences The presences in the match to send the state. - */ - virtual void sendMatchData( - const std::string& matchId, - std::int64_t opCode, - const NBytes& data, - const std::vector& presences = {} - ) = 0; - - /** - * Follow one or more users for status updates. - * - * @param userIds The user Ids to follow. - */ - virtual void followUsers( - const std::vector& userIds, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Unfollow status updates for one or more users. - * - * @param userIds The ids of users to unfollow. - */ - virtual void unfollowUsers( - const std::vector& userIds, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Update the user's status online. - * - * @param status The new status of the user. - */ - virtual void updateStatus( - const std::string& status, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Send an RPC message to the server. - * - * @param id The ID of the function to execute. - * @param payload The string content to send to the server. - */ - virtual void rpc( - const std::string& id, - const opt::optional& payload = opt::nullopt, - std::function successCallback = nullptr, - RtErrorCallback errorCallback = nullptr - ) = 0; - - /** - * Accept a party member's request to join the party. - * - * @param partyId The party ID to accept the join request for. - * @param presence The presence to accept as a party member. - */ - virtual void acceptPartyMember(const std::string& partyId, NUserPresence& presence, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Begin matchmaking as a party. - * @param partyId Party ID. - * @param query Filter query used to identify suitable users. - * @param minCount Minimum total user count to match together. - * @param maxCount Maximum total user count to match together. - * @param stringProperties String properties. - * @param numericProperties Numeric properties. - * @param countMultiple An optional multiple of the matched count that must be satisfied. - */ - virtual void addMatchmakerParty(const std::string& partyId, const std::string& query, int32_t minCount, int32_t maxCount, - const NStringMap& stringProperties = {}, const NStringDoubleMap& numericProperties = {}, - const opt::optional& countMultiple = opt::nullopt, - std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * End a party, kicking all party members and closing it. - * @param partyId The ID of the party. - */ - virtual void closeParty(const std::string& partyId, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Create a party. - * @param open Whether or not the party will require join requests to be approved by the party leader. - * @param maxSize Maximum number of party members. - */ - virtual void createParty(bool open, int maxSize, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Join a party. - * @param partyId Party ID. - */ - virtual void joinParty(const std::string& partyId, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Leave the party. - * @param partyId Party ID. - */ - virtual void leaveParty(const std::string& partyId, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Request a list of pending join requests for a party. - * @param partyId Party ID. - */ - virtual void listPartyJoinRequests(const std::string& partyId, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Promote a new party leader. - * @param partyId Party ID. - * @param partyMember The presence of an existing party member to promote as the new leader. - */ - virtual void promotePartyMember(const std::string& partyId, NUserPresence& partyMember, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Cancel a party matchmaking process using a ticket. - * @param partyId Party ID. - * @param ticket The ticket to cancel. - */ - virtual void removeMatchmakerParty(const std::string& partyId, const std::string& ticket, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Kick a party member, or decline a request to join. - * @param partyId Party ID to remove/reject from. - * @param presence The presence to remove or reject. - */ - virtual void removePartyMember(const std::string& partyId, NUserPresence& presence, std::function successCallback = nullptr, RtErrorCallback errorCallback = nullptr) = 0; - - /** - * Send data to a party. - * @param partyId Party ID to send to. - * @param opCode Op code value. - * @param data The input data to send from the byte buffer, if any. - */ - virtual void sendPartyData(const std::string& partyId, long opCode, NBytes& data) = 0; - - /** - * Join a chat channel on the server. - * - * @param target The target channel to join. - * @param type The type of channel to join. - * @param persistence True if chat messages should be stored. - * @param hidden True if the user should be hidden on the channel. - */ - virtual std::future joinChatAsync( - const std::string& target, - NChannelType type, - const opt::optional& persistence = opt::nullopt, - const opt::optional& hidden = opt::nullopt - ) = 0; - - /** - * Leave a chat channel on the server. - * - * @param channelId The channel to leave. - */ - virtual std::future leaveChatAsync( - const std::string& channelId - ) = 0; - - /** - * Send a chat message to a channel on the server. - * - * @param channelId The channel to send on. - * @param content The content of the chat message. Must be a JSON object. - */ - virtual std::future writeChatMessageAsync( - const std::string& channelId, - const std::string& content - ) = 0; - - /** - * Update a chat message to a channel on the server. - * - * @param channelId The ID of the chat channel with the message. - * @param messageId The ID of the message to update. - * @param content The content update for the message. Must be a JSON object. - */ - virtual std::future updateChatMessageAsync( - const std::string& channelId, - const std::string& messageId, - const std::string& content - ) = 0; - - /** - * Remove a chat message from a channel on the server. - * - * @param channelId The chat channel with the message. - * @param messageId The ID of a chat message to remove. - */ - virtual std::future removeChatMessageAsync( - const std::string& channelId, - const std::string& messageId - ) = 0; - - /** - * Create a multiplayer match on the server. - */ - virtual std::future createMatchAsync() = 0; - - /** - * Join a multiplayer match by ID. - * - * @param matchId A match ID. - */ - virtual std::future joinMatchAsync( - const std::string& matchId, - const NStringMap& metadata - ) = 0; - - /** - * Join a multiplayer match with a matchmaker. - * - * @param token A matchmaker ticket result object. - */ - virtual std::future joinMatchByTokenAsync( - const std::string& token - ) = 0; - - /** - * Leave a match on the server. - * - * @param matchId The match to leave. - */ - virtual std::future leaveMatchAsync( - const std::string& matchId - ) = 0; - - /** - * Join the matchmaker pool and search for opponents on the server. - * - * @param minCount The minimum number of players to compete against. - * @param maxCount The maximum number of players to compete against. - * @param query A matchmaker query to search for opponents. - * @param stringProperties A set of k/v properties to provide in searches. - * @param numericProperties A set of k/v numeric properties to provide in searches. - * @param countMultiple An optional multiple of the matched count that must be satisfied. - */ - virtual std::future addMatchmakerAsync( - const opt::optional& minCount = opt::nullopt, - const opt::optional& maxCount = opt::nullopt, - const opt::optional& query = opt::nullopt, - const NStringMap& stringProperties = {}, - const NStringDoubleMap& numericProperties = {}, - const opt::optional& countMultiple = opt::nullopt - ) = 0; - - /** - * Leave the matchmaker pool by ticket. - * - * @param ticket The ticket returned by the matchmaker on join. See NMatchmakerTicket.ticket. - */ - virtual std::future removeMatchmakerAsync( - const std::string& ticket - ) = 0; - - /** - * Send a state change to a match on the server. - * - * When no presences are supplied the new match state will be sent to all presences. - * - * @param matchId The Id of the match. - * @param opCode An operation code for the match state. - * @param data The new state to send to the match. - * @param presences The presences in the match to send the state. - */ - virtual std::future sendMatchDataAsync( - const std::string& matchId, - std::int64_t opCode, - const NBytes& data, - const std::vector& presences = {} - ) = 0; - - /** - * Follow one or more users for status updates. - * - * @param userIds The user Ids to follow. - */ - virtual std::future followUsersAsync( - const std::vector& userIds - ) = 0; - - /** - * Unfollow status updates for one or more users. - * - * @param userIds The ids of users to unfollow. - */ - virtual std::future unfollowUsersAsync( - const std::vector& userIds - ) = 0; - - /** - * Update the user's status online. - * - * @param status The new status of the user. - */ - virtual std::future updateStatusAsync( - const std::string& status - ) = 0; - - /** - * Send an RPC message to the server. - * - * @param id The ID of the function to execute. - * @param payload The string content to send to the server. - */ - virtual std::future rpcAsync( - const std::string& id, - const opt::optional& payload = opt::nullopt - ) = 0; - - /** - * Accept a party member's request to join the party. - * - * @param partyId The party ID to accept the join request for. - * @param presence The presence to accept as a party member. - */ - virtual std::future acceptPartyMemberAsync(const std::string& partyId, NUserPresence& presence) = 0; - - /** - * Begin matchmaking as a party. - * @param partyId Party ID. - * @param query Filter query used to identify suitable users. - * @param minCount Minimum total user count to match together. - * @param maxCount Maximum total user count to match together. - * @param stringProperties String properties. - * @param numericProperties Numeric properties. - * @param countMultiple An optional multiple of the matched count that must be satisfied. - */ - virtual std::future addMatchmakerPartyAsync(const std::string& partyId, const std::string& query, int32_t minCount, int32_t maxCount, - const NStringMap& stringProperties = {}, const NStringDoubleMap& numericProperties = {}, - const opt::optional& countMultiple = opt::nullopt) = 0; - - /** - * End a party, kicking all party members and closing it. - * @param partyId The ID of the party. - */ - virtual std::future closePartyAsync(const std::string& partyId) = 0; - - /** - * Create a party. - * @param open Whether or not the party will require join requests to be approved by the party leader. - * @param maxSize Maximum number of party members. - */ - virtual std::future createPartyAsync(bool open, int maxSize) = 0; - - /** - * Join a party. - * @param partyId Party ID. - */ - virtual std::future joinPartyAsync(const std::string& partyId) = 0; - - /** - * Leave the party. - * @param partyId Party ID. - */ - virtual std::future leavePartyAsync(const std::string& partyId) = 0; - - /** - * Request a list of pending join requests for a party. - * @param partyId Party ID. - */ - virtual std::future listPartyJoinRequestsAsync(const std::string& partyId) = 0; - - /** - * Promote a new party leader. - * @param partyId Party ID. - * @param partyMember The presence of an existing party member to promote as the new leader. - */ - virtual std::future promotePartyMemberAsync(const std::string& partyId, NUserPresence& partyMember) = 0; - - /** - * Cancel a party matchmaking process using a ticket. - * @param partyId Party ID. - * @param ticket The ticket to cancel. - */ - virtual std::future removeMatchmakerPartyAsync(const std::string& partyId, const std::string& ticket) = 0; - - /** - * Kick a party member, or decline a request to join. - * @param partyId Party ID to remove/reject from. - * @param presence The presence to remove or reject. - */ - virtual std::future removePartyMemberAsync(const std::string& partyId, NUserPresence& presence) = 0; - - /** - * Send data to a party. - * @param partyId Party ID to send to. - * @param opCode Op code value. - * @param data The input data to send from the byte buffer, if any. - */ - virtual std::future sendPartyDataAsync(const std::string& partyId, long opCode, NBytes& data) = 0; - }; - - using NRtClientPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientListenerInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientListenerInterface.h deleted file mode 100644 index c52877ba8..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtClientListenerInterface.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /** - * A listener for receiving NRtClientInterface events. - */ - class NRtClientListenerInterface - { - public: - virtual ~NRtClientListenerInterface() {} - - /** - * Called when the client socket has been connected. - */ - virtual void onConnect() {} - - /** - * Called when the client socket disconnects. - * - * @param info The NRtClientDisconnectInfo. - */ - virtual void onDisconnect(const NRtClientDisconnectInfo& info) { (void)info; } - - /** - * Called when the client receives an error. - * - * @param error The NRtError received. - */ - virtual void onError(const NRtError& error) { (void)error; } - - /** - * Called when a new channel message has been received. - * - * @param message The NChannelMessage received. - */ - virtual void onChannelMessage(const NChannelMessage& message) { (void)message; } - - /** - * Called when a new channel presence update has been received. - * - * @param presence The NChannelPresenceEvent received. - */ - virtual void onChannelPresence(const NChannelPresenceEvent& presence) { (void)presence; } - - /** - * Called when a matchmaking has found a match. - * - * @param matched The NMatchmakerMatched received. - */ - virtual void onMatchmakerMatched(NMatchmakerMatchedPtr matched) { (void)matched; } - - /** - * Called when a new match data is received. - * - * @param matchData The NMatchData received. - */ - virtual void onMatchData(const NMatchData& matchData) { (void)matchData; } - - /** - * Called when a new match presence update is received. - * - * @param matchPresence The NMatchPresenceEvent received. - */ - virtual void onMatchPresence(const NMatchPresenceEvent& matchPresence) { (void)matchPresence; } - - /** - * Called when the client receives new notifications. - * - * @param notifications The list of NNotification received. - */ - virtual void onNotifications(const NNotificationList& notifications) { (void)notifications; } - - /** - * Called when occur when the current user's invitation request is accepted - * by the party leader of a closed party. - * - * @param party the NParty joined by the user. - */ - virtual void onParty(const NParty& party) { (void) party; } - - /** - * Called when either the user's party closes or the user is removed from the party. - * - * @param partyClosedEvent The NPartyClose received. - */ - virtual void onPartyClosed(const NPartyClose& partyCloseEvent) { (void) partyCloseEvent; }; - - /** - * Called when the user receives custom party data. - * - * @param partyData The NPartyData received. - */ - virtual void onPartyData(const NPartyData& partyData) { (void) partyData; }; - - /** - * Called when the user receives a request to join the party. - * - * @param party The NPartyJoinRequest received. - */ - virtual void onPartyJoinRequest(const NPartyJoinRequest& partyJoinRequest) { (void) partyJoinRequest; }; - - /** - * Called when the user's party leader has changed. - * - * @param partyLeader the new NPartyLeader. - */ - virtual void onPartyLeader(const NPartyLeader& partyLeader) { (void) partyLeader; }; - - /** - * Called when the user receives a new party matchmaker ticket. - * - * @param ticket the NPartyMatchmakerTicket received upon entering the matchmaking system. - */ - virtual void onPartyMatchmakerTicket(const NPartyMatchmakerTicket& ticket) { (void) ticket; }; - - /** - * Called when a presence event occurs within the party. - * Received a new presence event in the party. - * - * @param presenceEvent the NPNPartyPresenceEvent received. - */ - virtual void onPartyPresence(const NPartyPresenceEvent& presenceEvent) { (void) presenceEvent; }; - - /** - * Called when the client receives status presence updates. - * - * @param presence Updated NStatusPresenceEvent presence. - */ - virtual void onStatusPresence(const NStatusPresenceEvent& presence) { (void)presence; } - - /** - * Called when the client receives stream presence updates. - * - * @param presence Updated NStreamPresenceEvent presence. - */ - virtual void onStreamPresence(const NStreamPresenceEvent& presence) { (void)presence; } - - /** - * Called when the client receives stream data. - * - * @param data Stream NStreamData data received. - */ - virtual void onStreamData(const NStreamData& data) { (void)data; } - }; - - using NRtClientListenerPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtDefaultClientListener.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtDefaultClientListener.h deleted file mode 100644 index cea555c06..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtDefaultClientListener.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /** - * A default listener for receiving NRtClientInterface events. - * It allows to set callbacks for needed events without creating own class. - */ - class NRtDefaultClientListener : public NRtClientListenerInterface - { - public: - using ConnectCallback = std::function; - using DisconnectCallback = std::function; - using ErrorCallback = std::function; - using ChannelMessageCallback = std::function; - using ChannelPresenceCallback = std::function; - using MatchmakerMatchedCallback = std::function; - using MatchDataCallback = std::function; - using MatchPresenceCallback = std::function; - using NotificationsCallback = std::function; - using PartyCallback = std::function; - using PartyClosedCallback = std::function; - using PartyDataCallback = std::function; - using PartyJoinRequestCallback = std::function; - using PartyLeaderCallback = std::function; - using PartyMatchmakerTicketCallback = std::function; - using PartyPresenceCallback = std::function; - using StatusPresenceCallback = std::function; - using StreamPresenceCallback = std::function; - using StreamDataCallback = std::function; - - void setConnectCallback(ConnectCallback callback) { _connectCallback = callback; } - void setDisconnectCallback(DisconnectCallback callback) { _disconnectCallback = callback; } - void setErrorCallback(ErrorCallback callback) { _errorCallback = callback; } - void setChannelMessageCallback(ChannelMessageCallback callback) { _channelMessageCallback = callback; } - void setChannelPresenceCallback(ChannelPresenceCallback callback) { _channelPresenceCallback = callback; } - void setMatchmakerMatchedCallback(MatchmakerMatchedCallback callback) { _matchmakerMatchedCallback = callback; } - void setMatchDataCallback(MatchDataCallback callback) { _matchDataCallback = callback; } - void setMatchPresenceCallback(MatchPresenceCallback callback) { _matchPresenceCallback = callback; } - void setNotificationsCallback(NotificationsCallback callback) { _notificationsCallback = callback; } - void setPartyCallback(PartyCallback callback) { _partyCallback = callback; } - void setPartyCloseCallback(PartyClosedCallback callback) { _partyClosedCallback = callback; } - void setPartyDataCallback(PartyDataCallback callback) { _partyDataCallback = callback; } - void setPartyJoinRequestCallback(PartyJoinRequestCallback callback) { _partyJoinRequestCallback = callback; } - void setPartyLeaderCallback(PartyLeaderCallback callback) { _partyLeaderCallback = callback; } - void setPartyMatchmakerTicketCallback(PartyMatchmakerTicketCallback callback) { _partyMatchmakerTicketCallback = callback; } - void setPartyPresenceCallback(PartyPresenceCallback callback) { _partyPresenceCallback = callback; } - void setStatusPresenceCallback(StatusPresenceCallback callback) { _statusPresenceCallback = callback; } - void setStreamPresenceCallback(StreamPresenceCallback callback) { _streamPresenceCallback = callback; } - void setStreamDataCallback(StreamDataCallback callback) { _streamDataCallback = callback; } - - protected: - void onConnect() override { if (_connectCallback) _connectCallback(); } - void onDisconnect(const NRtClientDisconnectInfo& info) override { if (_disconnectCallback) _disconnectCallback(info); } - void onError(const NRtError& error) override { if (_errorCallback) _errorCallback(error); } - void onChannelMessage(const NChannelMessage& message) override { if (_channelMessageCallback) _channelMessageCallback(message); } - void onChannelPresence(const NChannelPresenceEvent& presence) override { if (_channelPresenceCallback) _channelPresenceCallback(presence); } - void onMatchmakerMatched(NMatchmakerMatchedPtr matched) override { if (_matchmakerMatchedCallback) _matchmakerMatchedCallback(matched); } - void onMatchData(const NMatchData& matchData) override { if (_matchDataCallback) _matchDataCallback(matchData); } - void onMatchPresence(const NMatchPresenceEvent& matchPresence) override { if (_matchPresenceCallback) _matchPresenceCallback(matchPresence); } - void onNotifications(const NNotificationList& notifications) override { if (_notificationsCallback) _notificationsCallback(notifications); } - void onParty(const NParty& party) override { if (_partyCallback) _partyCallback(party); } - void onPartyClosed(const NPartyClose& partyClosed) override { if (_partyClosedCallback) _partyClosedCallback(partyClosed); } - void onPartyData(const NPartyData& partyData) override { if (_partyDataCallback) _partyDataCallback(partyData); } - void onPartyJoinRequest(const NPartyJoinRequest& partyJoinRequest) override { if (_partyJoinRequestCallback) _partyJoinRequestCallback(partyJoinRequest); } - void onPartyLeader(const NPartyLeader& partyLeader) override { if (_partyLeaderCallback) _partyLeaderCallback(partyLeader); } - void onPartyMatchmakerTicket(const NPartyMatchmakerTicket& partyMatchmakerTicket) override { if (_partyMatchmakerTicketCallback) _partyMatchmakerTicketCallback(partyMatchmakerTicket); } - void onPartyPresence(const NPartyPresenceEvent& partyPresence) override { if (_partyPresenceCallback) _partyPresenceCallback(partyPresence); } - void onStatusPresence(const NStatusPresenceEvent& presence) override { if (_statusPresenceCallback) _statusPresenceCallback(presence); } - void onStreamPresence(const NStreamPresenceEvent& presence) override { if (_streamPresenceCallback) _streamPresenceCallback(presence); } - void onStreamData(const NStreamData& data) override { if (_streamDataCallback) _streamDataCallback(data); } - - protected: - ConnectCallback _connectCallback; - DisconnectCallback _disconnectCallback; - ErrorCallback _errorCallback; - ChannelMessageCallback _channelMessageCallback; - ChannelPresenceCallback _channelPresenceCallback; - MatchmakerMatchedCallback _matchmakerMatchedCallback; - MatchDataCallback _matchDataCallback; - MatchPresenceCallback _matchPresenceCallback; - NotificationsCallback _notificationsCallback; - PartyCallback _partyCallback; - PartyClosedCallback _partyClosedCallback; - PartyDataCallback _partyDataCallback; - PartyJoinRequestCallback _partyJoinRequestCallback; - PartyLeaderCallback _partyLeaderCallback; - PartyMatchmakerTicketCallback _partyMatchmakerTicketCallback; - PartyPresenceCallback _partyPresenceCallback; - StatusPresenceCallback _statusPresenceCallback; - StreamPresenceCallback _streamPresenceCallback; - StreamDataCallback _streamDataCallback; - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtTransportInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtTransportInterface.h deleted file mode 100644 index ff26e910a..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NRtTransportInterface.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - enum class NRtTransportType - { - Binary, ///< used by `Protobuf` protocol - Text ///< used by `Json` protocol - }; - - /** - * A real-time transport interface to send and receive data. - */ - class NRtTransportInterface - { - public: - virtual ~NRtTransportInterface() {} - - using ConnectCallback = std::function; - using DisconnectCallback = std::function; - using ErrorCallback = std::function; - using MessageCallback = std::function; - - void setConnectCallback(ConnectCallback callback) { _connectCallback = callback; } - void setDisconnectCallback(DisconnectCallback callback) { _disconnectCallback = callback; } - void setErrorCallback(ErrorCallback callback) { _errorCallback = callback; } - void setMessageCallback(MessageCallback callback) { _messageCallback = callback; } - - /** - * Set activity timeout, milliseconds. - * - * If no any message (including ping) received from server during activity timeout - * then connection will be closed. - * Set 0 to disable (default value). - */ - virtual void setActivityTimeout(uint32_t timeoutMs) = 0; - - /** - * Get activity timeout, milliseconds. - */ - virtual uint32_t getActivityTimeout() const = 0; - - /** - * Pumps requests queue in your thread. - * NRtClientInterface will call this from it's `tick`. - */ - virtual void tick() = 0; - - /** - * Connect to the server. - * - * Expectations from the implementation are: - * - It MUST NOT trigger firOn* synchronous in this call - * - * @param url The URL of websocket server. - * @param type The transport type: Text or Binary. - */ - virtual void connect(const std::string& url, NRtTransportType type) = 0; - - /** - * @return True if connected to server. - */ - virtual bool isConnected() const { return _connected; } - - /** - * Close the connection with the server. - * - * Expectations from the implementation are: - * - It is possible to call connect after disconnect on the same instance - * - After disconnect returns any late messages arriving won't trigger messageCallback - * - It MUST NOT trigger fireOn* synchronous in this call - * - If connection is in progress and fireOnConnect is imminent, it MUST either schedule - * fireOnDisconnect to fire AFTER fireOnConnect or cancel it and fire neither. - * - */ - virtual void disconnect() = 0; - - /** - * Send bytes data to the server. - * - * @param data The byte data to send. - * @return True if sent successfully. - */ - virtual bool send(const NBytes& data) = 0; - - protected: - void fireOnConnected() { _connected = true; if (_connectCallback) _connectCallback(); } - void fireOnDisconnected(const NRtClientDisconnectInfo& info) { _connected = false; if (_disconnectCallback) _disconnectCallback(info); } - void fireOnError(const std::string& description) { if (_errorCallback) _errorCallback(description); } - void fireOnMessage(const NBytes& data) { if (_messageCallback) _messageCallback(data); } - - protected: - ConnectCallback _connectCallback; - DisconnectCallback _disconnectCallback; - ErrorCallback _errorCallback; - MessageCallback _messageCallback; - bool _connected = false; - }; - - using NRtTransportPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NWebsocketsFactory.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NWebsocketsFactory.h deleted file mode 100644 index 43f1c426e..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/NWebsocketsFactory.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#if defined BUILD_WEBSOCKET_WSLAY -#include -#endif - -NAKAMA_NAMESPACE_BEGIN - - /** - * Create default websocket transport. - * - */ -#if !defined(WITH_EXTERNAL_WS) && !defined(BUILD_IO_EXTERNAL) - NAKAMA_API NRtTransportPtr createDefaultWebsocket(const NPlatformParameters& platformParameters); -#endif - -#if defined BUILD_WEBSOCKET_WSLAY - NAKAMA_API NRtTransportPtr createWslayWebsocket(std::unique_ptr io); -#endif - - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannel.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannel.h deleted file mode 100644 index 5623c502b..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannel.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A realtime chat channel. - struct NChannel - { - std::string id; ///< The ID of the channel. - std::vector presences; ///< The users currently in the channel. - NUserPresence self; ///< A reference to the current user's presence in the channel. - std::string roomName; ///< The name of the chat room, or an empty string if this message was not sent through a chat room. - std::string groupId; ///< The ID of the group, or an empty string if this message was not sent through a group channel. - std::string userIdOne; ///< The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - std::string userIdTwo; ///< The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - }; - - using NChannelPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelMessageAck.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelMessageAck.h deleted file mode 100644 index 1cc73f753..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelMessageAck.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A receipt reply from a channel message send operation. - struct NChannelMessageAck - { - std::string channelId; ///< The channel the message was sent to. - std::string messageId; ///< The unique ID assigned to the message. - std::string username; ///< Username of the message sender. - int32_t code = 0; ///< The code representing a message type or category. - NTimestamp createTime = 0; ///< The UNIX time when the message was created. - NTimestamp updateTime = 0; ///< The UNIX time when the message was last updated. - bool persistent = false; ///< True if the message was persisted to the channel's history, false otherwise. - std::string roomName; ///< The name of the chat room, or an empty string if this message was not sent through a chat room. - std::string groupId; ///< The ID of the group, or an empty string if this message was not sent through a group channel. - std::string userIdOne; ///< The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - std::string userIdTwo; ///< The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelPresenceEvent.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelPresenceEvent.h deleted file mode 100644 index e48751430..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NChannelPresenceEvent.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A set of joins and leaves on a particular channel. - struct NChannelPresenceEvent - { - std::string channelId; ///< The channel identifier this event is for. - std::vector joins; ///< Presences joining the channel as part of this event, if any. - std::vector leaves; ///< Presences leaving the channel as part of this event, if any. - std::string roomName; ///< The name of the chat room, or an empty string if this message was not sent through a chat room. - std::string groupId; ///< The ID of the group, or an empty string if this message was not sent through a group channel. - std::string userIdOne; ///< The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - std::string userIdTwo; ///< The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchData.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchData.h deleted file mode 100644 index 83077d590..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchData.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Realtime match data received from the server. - struct NMatchData - { - std::string matchId; ///< The match unique ID. - NUserPresence presence; ///< A reference to the user presence that sent this data, if any. - std::int64_t opCode; ///< Op code value. - NBytes data; ///< Data payload, if any. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchPresenceEvent.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchPresenceEvent.h deleted file mode 100644 index 44f965153..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchPresenceEvent.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A set of joins and leaves on a particular realtime match. - struct NMatchPresenceEvent - { - std::string matchId; /// The match unique ID. - std::vector joins; /// User presences that have just joined the match. - std::vector leaves; /// User presences that have just left the match. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerMatched.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerMatched.h deleted file mode 100644 index d00795e90..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerMatched.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - struct NMatchmakerUser - { - NUserPresence presence; ///< User info. - NStringMap stringProperties; ///< String properties. - NStringDoubleMap numericProperties; ///< Numeric properties. - }; - - /// A successful matchmaking result. - struct NMatchmakerMatched - { - std::string ticket; ///< The matchmaking ticket that has completed. - ///< The match token or match ID to join. - std::string matchId; ///< Match ID. - std::string token; ///< Match join token. - std::vector users; ///< The users that have been matched together, and information about their matchmaking data. - NMatchmakerUser self; ///< A reference to the current user and their properties. - }; - - using NMatchmakerMatchedPtr = std::shared_ptr; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerTicket.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerTicket.h deleted file mode 100644 index 62648096e..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NMatchmakerTicket.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A ticket representing a new matchmaking process. - struct NMatchmakerTicket - { - std::string ticket; ///< The ticket that can be used to cancel matchmaking. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NParty.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NParty.h deleted file mode 100644 index 05a2be159..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NParty.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Information about a party. - struct NParty - { - std::string id; /// The unique party identifier. - - bool open; /// True, if the party is open to join. - - int maxSize; /// The maximum number of party members. - - NUserPresence self; /// The current user in this party. i.e. Yourself. - - NUserPresence leader; /// The current party leader. - - std::vector presences; /// All members currently in the party. - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyClose.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyClose.h deleted file mode 100644 index 432a56485..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyClose.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Information about a party close event. - struct NPartyClose - { - std::string id; /// The unique party identifier of the closing party. - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyData.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyData.h deleted file mode 100644 index 92be1ad03..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyData.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Incoming party data delivered from the server. - struct NPartyData - { - /// The ID of the party. - std::string partyId; - - /// A reference to the user presence that sent this data, if any. - NUserPresence presence; - - /// The operation code the message was sent with. - std::int64_t opCode; - - /// Data payload, if any. - NBytes data; - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyJoinRequest.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyJoinRequest.h deleted file mode 100644 index a53ded357..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyJoinRequest.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Incoming notification for one or more new presences attempting to join the party. - struct NPartyJoinRequest - { - /// The ID of the party to get a list of join requests for. - std::string partyId; - - /// Presences attempting to join, or who have joined. - std::vector presences; - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyLeader.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyLeader.h deleted file mode 100644 index fb9bb5201..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyLeader.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Announcement of a new party leader. - struct NPartyLeader - { - /// The ID of the party to announce the new leader for. - std::string partyId; - - /// The presence of the new party leader. - NUserPresence presence; - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyMatchmakerTicket.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyMatchmakerTicket.h deleted file mode 100644 index bfd215439..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyMatchmakerTicket.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A response from starting a new party matchmaking process. - struct NPartyMatchmakerTicket - { - /// The ID of the party. - std::string partyId; - - /// The ticket that can be used to cancel matchmaking. - std::string ticket; - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyPresenceEvent.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyPresenceEvent.h deleted file mode 100644 index 0c3f94ccf..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NPartyPresenceEvent.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Presence update for a particular party. - struct NPartyPresenceEvent - { - /// The ID of the party. - std::string partyId; - - /// The user presences that have just joined the party. - std::vector joins; - - /// The user presences that have just left the party. - std::vector leaves; - }; - -NAKAMA_NAMESPACE_END \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtError.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtError.h deleted file mode 100644 index 4b0c305d3..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtError.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// The selection of possible error codes. - enum class RtErrorCode - { - UNKNOWN = -100, - - // client side errors - CONNECT_ERROR = -1, ///< Connect has failed. - TRANSPORT_ERROR = -2, ///< Transport error. - DISCONNECTED = -3, ///< Request cancelled due to transport disconnect - - // server side errors - RUNTIME_EXCEPTION = 0, ///< An unexpected result from the server. - UNRECOGNIZED_PAYLOAD = 1, ///< The server received a message which is not recognised. - MISSING_PAYLOAD = 2, ///< A message was expected but contains no content. - BAD_INPUT = 3, ///< Fields in the message have an invalid format. - MATCH_NOT_FOUND = 4, ///< The match id was not found. - MATCH_JOIN_REJECTED = 5, ///< The match join was rejected. - RUNTIME_FUNCTION_NOT_FOUND = 6, ///< The runtime function does not exist on the server. - RUNTIME_FUNCTION_EXCEPTION = 7 ///< The runtime function executed with an error. - }; - - /// A logical error which may occur on the server. - struct NRtError - { - NRtError() {} - NRtError(RtErrorCode code, const std::string& message) : code(code), message(message) {} - NRtError(RtErrorCode code, std::string&& message) : code(code), message(std::move(message)) {} - - RtErrorCode code = RtErrorCode::UNKNOWN; ///< The error code - std::string message; ///< A message in English to help developers debug the response. - NStringMap context; ///< Additional error details which may be different for each response. - }; - - NAKAMA_API const char* toString(RtErrorCode code); - NAKAMA_API std::string toString(const NRtError& error); - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtException.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtException.h deleted file mode 100644 index 82fdabed0..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NRtException.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "NRtError.h" - -namespace Nakama -{ - class NRtException : public std::runtime_error { - public: - NRtException(const NRtError& error) - : std::runtime_error(error.message), error(error) {} - - const NRtError error; - }; -} diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatus.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatus.h deleted file mode 100644 index 5b94bee7e..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatus.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A snapshot of statuses for some set of users. - struct NStatus - { - std::vector presences; ///< User statuses. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatusPresenceEvent.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatusPresenceEvent.h deleted file mode 100644 index 4108ecec5..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStatusPresenceEvent.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A batch of status updates for a given user. - struct NStatusPresenceEvent - { - std::vector joins; ///< New statuses for the user. - std::vector leaves; ///< Previous statuses for the user. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStream.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStream.h deleted file mode 100644 index ea4fec49e..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStream.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// Represents identifying information for a stream. - struct NStream - { - int32_t mode = 0; ///< Mode identifies the type of stream. - std::string subject; ///< Subject is the primary identifier, if any. - std::string subcontext; ///< Subcontext is a secondary identifier, if any. - std::string label; ///< The label is an arbitrary identifying string, if the stream has one. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamData.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamData.h deleted file mode 100644 index fcc8371b9..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamData.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A data message delivered over a stream. - struct NStreamData - { - NStream stream; ///< The stream this data message relates to. - NUserPresence sender; ///< The sender, if any. - std::string data; ///< Arbitrary contents of the data message. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamPresenceEvent.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamPresenceEvent.h deleted file mode 100644 index 009b3e75c..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NStreamPresenceEvent.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A set of joins and leaves on a particular stream. - struct NStreamPresenceEvent - { - NStream stream; ///< The stream this event relates to. - std::vector joins; ///< Presences joining the stream as part of this event, if any. - std::vector leaves; ///< Presences leaving the stream as part of this event, if any. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NUserPresence.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NUserPresence.h deleted file mode 100644 index 607cca436..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/rtdata/NUserPresence.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include - -NAKAMA_NAMESPACE_BEGIN - - /// A user session associated to a stream, usually through a list operation or a join/leave event. - struct NUserPresence - { - std::string userId; ///< The user this presence belongs to. - std::string sessionId; ///< A unique session ID identifying the particular connection, because the user may have many. - std::string username; ///< The username for display purposes. - bool persistence = false; ///< Whether this presence generates persistent data/messages, if applicable for the stream type. - std::string status; ///< A user-set status message for this stream, if applicable. - }; - -NAKAMA_NAMESPACE_END diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/wslay/WslayIOInterface.h b/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/wslay/WslayIOInterface.h deleted file mode 100644 index d6829587a..000000000 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/realtime/wslay/WslayIOInterface.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "nakama-cpp/URLParts.h" - -namespace Nakama { - -// Result of async operation -enum class NetIOAsyncResult { - ERR = -1, // error, no further progress possible - AGAIN = 0, // to be called again - DONE = 1, // successful completion -}; - -class WslayIOInterface { -public: - virtual ~WslayIOInterface() {} - virtual int recv(void* buf, size_t len, int* wouldBlock) = 0; - virtual int send(const void* data, size_t len, int* wouldBlock) = 0; - virtual void close() = 0; - virtual NetIOAsyncResult connect_init(const URLParts& urlParts) = 0; - virtual NetIOAsyncResult connect_tick() = 0; -}; - -} \ No newline at end of file diff --git a/Nakama/Source/NakamaCore/Public/nonstd/optional.hpp b/Nakama/Source/NakamaCore/Public/nonstd/optional.hpp deleted file mode 100644 index c8a903811..000000000 --- a/Nakama/Source/NakamaCore/Public/nonstd/optional.hpp +++ /dev/null @@ -1,1807 +0,0 @@ -// -// Copyright (c) 2014-2021 Martin Moene -// -// https://github.com/martinmoene/optional-lite -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#pragma once - -#ifndef NONSTD_OPTIONAL_LITE_HPP -#define NONSTD_OPTIONAL_LITE_HPP - -#define optional_lite_MAJOR 3 -#define optional_lite_MINOR 5 -#define optional_lite_PATCH 0 - -#define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH) - -#define optional_STRINGIFY( x ) optional_STRINGIFY_( x ) -#define optional_STRINGIFY_( x ) #x - -// optional-lite configuration: - -#define optional_OPTIONAL_DEFAULT 0 -#define optional_OPTIONAL_NONSTD 1 -#define optional_OPTIONAL_STD 2 - -// tweak header support: - -#ifdef __has_include -# if __has_include() -# include -# endif -#define optional_HAVE_TWEAK_HEADER 1 -#else -#define optional_HAVE_TWEAK_HEADER 0 -//# pragma message("optional.hpp: Note: Tweak header not supported.") -#endif - -// optional selection and configuration: - -#if !defined( optional_CONFIG_SELECT_OPTIONAL ) -# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD ) -#endif - -// Control presence of exception handling (try and auto discover): - -#ifndef optional_CONFIG_NO_EXCEPTIONS -# if defined(_MSC_VER) -# include // for _HAS_EXCEPTIONS -# endif -# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS)) -# define optional_CONFIG_NO_EXCEPTIONS 0 -# else -# define optional_CONFIG_NO_EXCEPTIONS 1 -# endif -#endif - -// C++ language version detection (C++20 is speculative): -// Note: VC14.0/1900 (VS2015) lacks too much from C++14. - -#ifndef optional_CPLUSPLUS -# if defined(_MSVC_LANG ) && !defined(__clang__) -# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) -# else -# define optional_CPLUSPLUS __cplusplus -# endif -#endif - -#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L ) -#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L ) -#define optional_CPP11_OR_GREATER_ ( optional_CPLUSPLUS >= 201103L ) -#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L ) -#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L ) -#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L ) - -// C++ language version (represent 98 as 3): - -#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) ) - -// Use C++17 std::optional if available and requested: - -#if optional_CPP17_OR_GREATER && defined(__has_include ) -# if __has_include( ) -# define optional_HAVE_STD_OPTIONAL 1 -# else -# define optional_HAVE_STD_OPTIONAL 0 -# endif -#else -# define optional_HAVE_STD_OPTIONAL 0 -#endif - -#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) ) - -// -// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: -// - -#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES -#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 - -// C++17 std::in_place in : - -#if optional_CPP17_OR_GREATER - -#include - -namespace nonstd { - -using std::in_place; -using std::in_place_type; -using std::in_place_index; -using std::in_place_t; -using std::in_place_type_t; -using std::in_place_index_t; - -#define nonstd_lite_in_place_t( T) std::in_place_t -#define nonstd_lite_in_place_type_t( T) std::in_place_type_t -#define nonstd_lite_in_place_index_t(K) std::in_place_index_t - -#define nonstd_lite_in_place( T) std::in_place_t{} -#define nonstd_lite_in_place_type( T) std::in_place_type_t{} -#define nonstd_lite_in_place_index(K) std::in_place_index_t{} - -} // namespace nonstd - -#else // optional_CPP17_OR_GREATER - -#include - -namespace nonstd { -namespace detail { - -template< class T > -struct in_place_type_tag {}; - -template< std::size_t K > -struct in_place_index_tag {}; - -} // namespace detail - -struct in_place_t {}; - -template< class T > -inline in_place_t in_place( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) -{ - return in_place_t(); -} - -template< std::size_t K > -inline in_place_t in_place( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) -{ - return in_place_t(); -} - -template< class T > -inline in_place_t in_place_type( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) -{ - return in_place_t(); -} - -template< std::size_t K > -inline in_place_t in_place_index( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) -{ - return in_place_t(); -} - -// mimic templated typedef: - -#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) -#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) -#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) - -#define nonstd_lite_in_place( T) nonstd::in_place_type -#define nonstd_lite_in_place_type( T) nonstd::in_place_type -#define nonstd_lite_in_place_index(K) nonstd::in_place_index - -} // namespace nonstd - -#endif // optional_CPP17_OR_GREATER -#endif // nonstd_lite_HAVE_IN_PLACE_TYPES - -// -// Using std::optional: -// - -#if optional_USES_STD_OPTIONAL - -#include - -namespace nonstd { - - using std::optional; - using std::bad_optional_access; - using std::hash; - - using std::nullopt; - using std::nullopt_t; - - using std::operator==; - using std::operator!=; - using std::operator<; - using std::operator<=; - using std::operator>; - using std::operator>=; - using std::make_optional; - using std::swap; -} - -#else // optional_USES_STD_OPTIONAL - -#include -#include - -// optional-lite alignment configuration: - -#ifndef optional_CONFIG_MAX_ALIGN_HACK -# define optional_CONFIG_MAX_ALIGN_HACK 0 -#endif - -#ifndef optional_CONFIG_ALIGN_AS -// no default, used in #if defined() -#endif - -#ifndef optional_CONFIG_ALIGN_AS_FALLBACK -# define optional_CONFIG_ALIGN_AS_FALLBACK double -#endif - -// Compiler warning suppression: - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wundef" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wundef" -#elif defined(_MSC_VER ) -# pragma warning( push ) -#endif - -// half-open range [lo..hi): -#define optional_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) - -// Compiler versions: -// -// MSVC++ 6.0 _MSC_VER == 1200 optional_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) -// MSVC++ 7.0 _MSC_VER == 1300 optional_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) -// MSVC++ 7.1 _MSC_VER == 1310 optional_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) -// MSVC++ 8.0 _MSC_VER == 1400 optional_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) -// MSVC++ 9.0 _MSC_VER == 1500 optional_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) -// MSVC++ 10.0 _MSC_VER == 1600 optional_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) -// MSVC++ 11.0 _MSC_VER == 1700 optional_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) -// MSVC++ 12.0 _MSC_VER == 1800 optional_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) -// MSVC++ 14.0 _MSC_VER == 1900 optional_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) -// MSVC++ 14.1 _MSC_VER >= 1910 optional_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) -// MSVC++ 14.2 _MSC_VER >= 1920 optional_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) - -#if defined(_MSC_VER ) && !defined(__clang__) -# define optional_COMPILER_MSVC_VER (_MSC_VER ) -# define optional_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) -#else -# define optional_COMPILER_MSVC_VER 0 -# define optional_COMPILER_MSVC_VERSION 0 -#endif - -#define optional_COMPILER_VERSION( major, minor, patch ) ( 10 * (10 * (major) + (minor) ) + (patch) ) - -#if defined(__GNUC__) && !defined(__clang__) -# define optional_COMPILER_GNUC_VERSION optional_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else -# define optional_COMPILER_GNUC_VERSION 0 -#endif - -#if defined(__clang__) -# define optional_COMPILER_CLANG_VERSION optional_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) -#else -# define optional_COMPILER_CLANG_VERSION 0 -#endif - -#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 140 ) -# pragma warning( disable: 4345 ) // initialization behavior changed -#endif - -#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 150 ) -# pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const' -#endif - -// Presence of language and library features: - -#define optional_HAVE(FEATURE) ( optional_HAVE_##FEATURE ) - -#ifdef _HAS_CPP0X -# define optional_HAS_CPP0X _HAS_CPP0X -#else -# define optional_HAS_CPP0X 0 -#endif - -// Unless defined otherwise below, consider VC14 as C++11 for optional-lite: - -#if optional_COMPILER_MSVC_VER >= 1900 -# undef optional_CPP11_OR_GREATER -# define optional_CPP11_OR_GREATER 1 -#endif - -#define optional_CPP11_90 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1500) -#define optional_CPP11_100 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1600) -#define optional_CPP11_110 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1700) -#define optional_CPP11_120 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1800) -#define optional_CPP11_140 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1900) -#define optional_CPP11_141 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1910) - -#define optional_CPP14_000 (optional_CPP14_OR_GREATER) -#define optional_CPP17_000 (optional_CPP17_OR_GREATER) - -// clang >= 2.9, gcc >= 4.9, msvc >= vc14.0/1900 (vs15): -#define optional_CPP11_140_C290_G490 ((optional_CPP11_OR_GREATER_ && (optional_COMPILER_CLANG_VERSION >= 290 || optional_COMPILER_GNUC_VERSION >= 490)) || (optional_COMPILER_MSVC_VER >= 1900)) - -// clang >= 3.5, msvc >= vc11 (vs12): -#define optional_CPP11_110_C350 ( optional_CPP11_110 && !optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) ) - -// clang >= 3.5, gcc >= 5.0, msvc >= vc11 (vs12): -#define optional_CPP11_110_C350_G500 \ - ( optional_CPP11_110 && \ - !( optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) \ - || optional_BETWEEN( optional_COMPILER_GNUC_VERSION , 1, 500 ) ) ) - -// Presence of C++11 language features: - -#define optional_HAVE_CONSTEXPR_11 optional_CPP11_140 -#define optional_HAVE_IS_DEFAULT optional_CPP11_140 -#define optional_HAVE_NOEXCEPT optional_CPP11_140 -#define optional_HAVE_NULLPTR optional_CPP11_100 -#define optional_HAVE_REF_QUALIFIER optional_CPP11_140_C290_G490 -#define optional_HAVE_STATIC_ASSERT optional_CPP11_110 -#define optional_HAVE_INITIALIZER_LIST optional_CPP11_140 - -// Presence of C++14 language features: - -#define optional_HAVE_CONSTEXPR_14 optional_CPP14_000 - -// Presence of C++17 language features: - -#define optional_HAVE_NODISCARD optional_CPP17_000 - -// Presence of C++ library features: - -#define optional_HAVE_CONDITIONAL optional_CPP11_120 -#define optional_HAVE_REMOVE_CV optional_CPP11_120 -#define optional_HAVE_TYPE_TRAITS optional_CPP11_90 - -#define optional_HAVE_TR1_TYPE_TRAITS (!! optional_COMPILER_GNUC_VERSION ) -#define optional_HAVE_TR1_ADD_POINTER (!! optional_COMPILER_GNUC_VERSION ) - -#define optional_HAVE_IS_ASSIGNABLE optional_CPP11_110_C350 -#define optional_HAVE_IS_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 -#define optional_HAVE_IS_NOTHROW_MOVE_ASSIGNABLE optional_CPP11_110_C350 -#define optional_HAVE_IS_NOTHROW_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 -#define optional_HAVE_IS_TRIVIALLY_COPY_CONSTRUCTIBLE optional_CPP11_110_C350_G500 -#define optional_HAVE_IS_TRIVIALLY_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350_G500 - -// C++ feature usage: - -#if optional_HAVE( CONSTEXPR_11 ) -# define optional_constexpr constexpr -#else -# define optional_constexpr /*constexpr*/ -#endif - -#if optional_HAVE( IS_DEFAULT ) -# define optional_is_default = default; -#else -# define optional_is_default {} -#endif - -#if optional_HAVE( CONSTEXPR_14 ) -# define optional_constexpr14 constexpr -#else -# define optional_constexpr14 /*constexpr*/ -#endif - -#if optional_HAVE( NODISCARD ) -# define optional_nodiscard [[nodiscard]] -#else -# define optional_nodiscard /*[[nodiscard]]*/ -#endif - -#if optional_HAVE( NOEXCEPT ) -# define optional_noexcept noexcept -#else -# define optional_noexcept /*noexcept*/ -#endif - -#if optional_HAVE( NULLPTR ) -# define optional_nullptr nullptr -#else -# define optional_nullptr NULL -#endif - -#if optional_HAVE( REF_QUALIFIER ) -// NOLINTNEXTLINE( bugprone-macro-parentheses ) -# define optional_ref_qual & -# define optional_refref_qual && -#else -# define optional_ref_qual /*&*/ -# define optional_refref_qual /*&&*/ -#endif - -#if optional_HAVE( STATIC_ASSERT ) -# define optional_static_assert(expr, text) static_assert(expr, text); -#else -# define optional_static_assert(expr, text) /*static_assert(expr, text);*/ -#endif - -// additional includes: - -#if optional_CONFIG_NO_EXCEPTIONS -// already included: -#else -# include -#endif - -#if optional_CPP11_OR_GREATER -# include -#endif - -#if optional_HAVE( INITIALIZER_LIST ) -# include -#endif - -#if optional_HAVE( TYPE_TRAITS ) -# include -#elif optional_HAVE( TR1_TYPE_TRAITS ) -# include -#endif - -// Method enabling - -#if optional_CPP11_OR_GREATER - -#define optional_REQUIRES_0(...) \ - template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > - -#define optional_REQUIRES_T(...) \ - , typename std::enable_if< (__VA_ARGS__), int >::type = 0 - -#define optional_REQUIRES_R(R, ...) \ - typename std::enable_if< (__VA_ARGS__), R>::type - -#define optional_REQUIRES_A(...) \ - , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr - -#endif - -// -// optional: -// - -namespace nonstd { namespace optional_lite { - -namespace std11 { - -template< class T, T v > struct integral_constant { enum { value = v }; }; -template< bool B > struct bool_constant : integral_constant{}; - -typedef bool_constant< true > true_type; -typedef bool_constant< false > false_type; - -#if optional_CPP11_OR_GREATER - using std::move; -#else - template< typename T > T & move( T & t ) { return t; } -#endif - -#if optional_HAVE( CONDITIONAL ) - using std::conditional; -#else - template< bool B, typename T, typename F > struct conditional { typedef T type; }; - template< typename T, typename F > struct conditional { typedef F type; }; -#endif // optional_HAVE_CONDITIONAL - -#if optional_HAVE( IS_ASSIGNABLE ) - using std::is_assignable; -#else - template< class T, class U > struct is_assignable : std11::true_type{}; -#endif - -#if optional_HAVE( IS_MOVE_CONSTRUCTIBLE ) - using std::is_move_constructible; -#else - template< class T > struct is_move_constructible : std11::true_type{}; -#endif - -#if optional_HAVE( IS_NOTHROW_MOVE_ASSIGNABLE ) - using std::is_nothrow_move_assignable; -#else - template< class T > struct is_nothrow_move_assignable : std11::true_type{}; -#endif - -#if optional_HAVE( IS_NOTHROW_MOVE_CONSTRUCTIBLE ) - using std::is_nothrow_move_constructible; -#else - template< class T > struct is_nothrow_move_constructible : std11::true_type{}; -#endif - -#if optional_HAVE( IS_TRIVIALLY_COPY_CONSTRUCTIBLE ) - using std::is_trivially_copy_constructible; -#else - template< class T > struct is_trivially_copy_constructible : std11::true_type{}; -#endif - -#if optional_HAVE( IS_TRIVIALLY_MOVE_CONSTRUCTIBLE ) - using std::is_trivially_move_constructible; -#else - template< class T > struct is_trivially_move_constructible : std11::true_type{}; -#endif - -} // namespace std11 - -#if optional_CPP11_OR_GREATER - -/// type traits C++17: - -namespace std17 { - -#if optional_CPP17_OR_GREATER - -using std::is_swappable; -using std::is_nothrow_swappable; - -#elif optional_CPP11_OR_GREATER - -namespace detail { - -using std::swap; - -struct is_swappable -{ - template< typename T, typename = decltype( swap( std::declval(), std::declval() ) ) > - static std11::true_type test( int /*unused*/ ); - - template< typename > - static std11::false_type test(...); -}; - -struct is_nothrow_swappable -{ - // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015): - - template< typename T > - static constexpr bool satisfies() - { - return noexcept( swap( std::declval(), std::declval() ) ); - } - - template< typename T > - static auto test( int /*unused*/ ) -> std11::integral_constant()>{} - - template< typename > - static auto test(...) -> std11::false_type; -}; - -} // namespace detail - -// is [nothow] swappable: - -template< typename T > -struct is_swappable : decltype( detail::is_swappable::test(0) ){}; - -template< typename T > -struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test(0) ){}; - -#endif // optional_CPP17_OR_GREATER - -} // namespace std17 - -/// type traits C++20: - -namespace std20 { - -template< typename T > -struct remove_cvref -{ - typedef typename std::remove_cv< typename std::remove_reference::type >::type type; -}; - -} // namespace std20 - -#endif // optional_CPP11_OR_GREATER - -/// class optional - -template< typename T > -class optional; - -namespace detail { - -// C++11 emulation: - -struct nulltype{}; - -template< typename Head, typename Tail > -struct typelist -{ - typedef Head head; - typedef Tail tail; -}; - -#if optional_CONFIG_MAX_ALIGN_HACK - -// Max align, use most restricted type for alignment: - -#define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ ) -#define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line ) -#define optional_UNIQUE3( name, line ) name ## line - -#define optional_ALIGN_TYPE( type ) \ - type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st ) - -template< typename T > -struct struct_t { T _; }; - -union max_align_t -{ - optional_ALIGN_TYPE( char ); - optional_ALIGN_TYPE( short int ); - optional_ALIGN_TYPE( int ); - optional_ALIGN_TYPE( long int ); - optional_ALIGN_TYPE( float ); - optional_ALIGN_TYPE( double ); - optional_ALIGN_TYPE( long double ); - optional_ALIGN_TYPE( char * ); - optional_ALIGN_TYPE( short int * ); - optional_ALIGN_TYPE( int * ); - optional_ALIGN_TYPE( long int * ); - optional_ALIGN_TYPE( float * ); - optional_ALIGN_TYPE( double * ); - optional_ALIGN_TYPE( long double * ); - optional_ALIGN_TYPE( void * ); - -#ifdef HAVE_LONG_LONG - optional_ALIGN_TYPE( long long ); -#endif - - struct Unknown; - - Unknown ( * optional_UNIQUE(_) )( Unknown ); - Unknown * Unknown::* optional_UNIQUE(_); - Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown ); - - struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_); - struct_t< Unknown * Unknown::* > optional_UNIQUE(_); - struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_); -}; - -#undef optional_UNIQUE -#undef optional_UNIQUE2 -#undef optional_UNIQUE3 - -#undef optional_ALIGN_TYPE - -#elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK - -// Use user-specified type for alignment: - -#define optional_ALIGN_AS( unused ) \ - optional_CONFIG_ALIGN_AS - -#else // optional_CONFIG_MAX_ALIGN_HACK - -// Determine POD type to use for alignment: - -#define optional_ALIGN_AS( to_align ) \ - typename type_of_size< alignment_types, alignment_of< to_align >::value >::type - -template< typename T > -struct alignment_of; - -template< typename T > -struct alignment_of_hack -{ - char c; - T t; - alignment_of_hack(); -}; - -template< size_t A, size_t S > -struct alignment_logic -{ - enum { value = A < S ? A : S }; -}; - -template< typename T > -struct alignment_of -{ - enum { value = alignment_logic< - sizeof( alignment_of_hack ) - sizeof(T), sizeof(T) >::value }; -}; - -template< typename List, size_t N > -struct type_of_size -{ - typedef typename std11::conditional< - N == sizeof( typename List::head ), - typename List::head, - typename type_of_size::type >::type type; -}; - -template< size_t N > -struct type_of_size< nulltype, N > -{ - typedef optional_CONFIG_ALIGN_AS_FALLBACK type; -}; - -template< typename T> -struct struct_t { T _; }; - -#define optional_ALIGN_TYPE( type ) \ - typelist< type , typelist< struct_t< type > - -struct Unknown; - -typedef - optional_ALIGN_TYPE( char ), - optional_ALIGN_TYPE( short ), - optional_ALIGN_TYPE( int ), - optional_ALIGN_TYPE( long ), - optional_ALIGN_TYPE( float ), - optional_ALIGN_TYPE( double ), - optional_ALIGN_TYPE( long double ), - - optional_ALIGN_TYPE( char *), - optional_ALIGN_TYPE( short * ), - optional_ALIGN_TYPE( int * ), - optional_ALIGN_TYPE( long * ), - optional_ALIGN_TYPE( float * ), - optional_ALIGN_TYPE( double * ), - optional_ALIGN_TYPE( long double * ), - - optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ), - optional_ALIGN_TYPE( Unknown * Unknown::* ), - optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ), - - nulltype - > > > > > > > > > > > > > > - > > > > > > > > > > > > > > - > > > > > > - alignment_types; - -#undef optional_ALIGN_TYPE - -#endif // optional_CONFIG_MAX_ALIGN_HACK - -/// C++03 constructed union to hold value. - -template< typename T > -union storage_t -{ -//private: -// template< typename > friend class optional; - - typedef T value_type; - - storage_t() optional_is_default - - explicit storage_t( value_type const & v ) - { - construct_value( v ); - } - - void construct_value( value_type const & v ) - { - ::new( value_ptr() ) value_type( v ); - } - -#if optional_CPP11_OR_GREATER - - explicit storage_t( value_type && v ) - { - construct_value( std::move( v ) ); - } - - void construct_value( value_type && v ) - { - ::new( value_ptr() ) value_type( std::move( v ) ); - } - - template< class... Args > - storage_t( nonstd_lite_in_place_t(T), Args&&... args ) - { - emplace( std::forward(args)... ); - } - - template< class... Args > - void emplace( Args&&... args ) - { - ::new( value_ptr() ) value_type( std::forward(args)... ); - } - - template< class U, class... Args > - void emplace( std::initializer_list il, Args&&... args ) - { - ::new( value_ptr() ) value_type( il, std::forward(args)... ); - } - -#endif - - void destruct_value() - { - value_ptr()->~T(); - } - - optional_nodiscard value_type const * value_ptr() const - { - return as(); - } - - value_type * value_ptr() - { - return as(); - } - - optional_nodiscard value_type const & value() const optional_ref_qual - { - return * value_ptr(); - } - - value_type & value() optional_ref_qual - { - return * value_ptr(); - } - -#if optional_HAVE( REF_QUALIFIER ) - - optional_nodiscard value_type const && value() const optional_refref_qual - { - return std::move( value() ); - } - - value_type && value() optional_refref_qual - { - return std::move( value() ); - } - -#endif - -#if optional_CPP11_OR_GREATER - - using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type; - aligned_storage_t data; - -#elif optional_CONFIG_MAX_ALIGN_HACK - - typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t; - - max_align_t hack; - aligned_storage_t data; - -#else - typedef optional_ALIGN_AS(value_type) align_as_type; - - typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t; - aligned_storage_t data; - -# undef optional_ALIGN_AS - -#endif // optional_CONFIG_MAX_ALIGN_HACK - - optional_nodiscard void * ptr() optional_noexcept - { - return &data; - } - - optional_nodiscard void const * ptr() const optional_noexcept - { - return &data; - } - - template - optional_nodiscard U * as() - { - return reinterpret_cast( ptr() ); - } - - template - optional_nodiscard U const * as() const - { - return reinterpret_cast( ptr() ); - } -}; - -} // namespace detail - -/// disengaged state tag - -struct nullopt_t -{ - struct init{}; - explicit optional_constexpr nullopt_t( init /*unused*/ ) optional_noexcept {} -}; - -#if optional_HAVE( CONSTEXPR_11 ) -constexpr nullopt_t nullopt{ nullopt_t::init{} }; -#else -// extra parenthesis to prevent the most vexing parse: -const nullopt_t nullopt(( nullopt_t::init() )); -#endif - -/// optional access error - -#if ! optional_CONFIG_NO_EXCEPTIONS - -class bad_optional_access : public std::logic_error -{ -public: - explicit bad_optional_access() - : logic_error( "bad optional access" ) {} -}; - -#endif //optional_CONFIG_NO_EXCEPTIONS - -/// optional - -template< typename T> -class optional -{ - optional_static_assert(( !std::is_same::type, nullopt_t>::value ), - "T in optional must not be of type 'nullopt_t'.") - - optional_static_assert(( !std::is_same::type, in_place_t>::value ), - "T in optional must not be of type 'in_place_t'.") - - optional_static_assert(( std::is_object::value && std::is_destructible::value && !std::is_array::value ), - "T in optional must meet the Cpp17Destructible requirements.") - -private: - template< typename > friend class optional; - - typedef void (optional::*safe_bool)() const; - -public: - typedef T value_type; - - // x.x.3.1, constructors - - // 1a - default construct - optional_constexpr optional() optional_noexcept - : has_value_( false ) - , contained() - {} - - // 1b - construct explicitly empty - // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) - optional_constexpr optional( nullopt_t /*unused*/ ) optional_noexcept - : has_value_( false ) - , contained() - {} - - // 2 - copy-construct -#if optional_CPP11_OR_GREATER - // template< typename U = T - // optional_REQUIRES_T( - // std::is_copy_constructible::value - // || std11::is_trivially_copy_constructible::value - // ) - // > -#endif - optional_constexpr14 optional( optional const & other ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( other.contained.value() ); - } - } - -#if optional_CPP11_OR_GREATER - - // 3 (C++11) - move-construct from optional - template< typename U = T - optional_REQUIRES_T( - std11::is_move_constructible::value - || std11::is_trivially_move_constructible::value - ) - > - optional_constexpr14 optional( optional && other ) - // NOLINTNEXTLINE( performance-noexcept-move-constructor ) - noexcept( std11::is_nothrow_move_constructible::value ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( std::move( other.contained.value() ) ); - } - } - - // 4a (C++11) - explicit converting copy-construct from optional - template< typename U - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && !std::is_convertible< U const & , T>::value /*=> explicit */ - ) - > - explicit optional( optional const & other ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( T{ other.contained.value() } ); - } - } -#endif // optional_CPP11_OR_GREATER - - // 4b (C++98 and later) - non-explicit converting copy-construct from optional - template< typename U -#if optional_CPP11_OR_GREATER - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && std::is_convertible< U const & , T>::value /*=> non-explicit */ - ) -#endif // optional_CPP11_OR_GREATER - > - // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) - /*non-explicit*/ optional( optional const & other ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( other.contained.value() ); - } - } - -#if optional_CPP11_OR_GREATER - - // 5a (C++11) - explicit converting move-construct from optional - template< typename U - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && !std::is_convertible< U &&, T>::value /*=> explicit */ - ) - > - explicit optional( optional && other - ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( T{ std::move( other.contained.value() ) } ); - } - } - - // 5a (C++11) - non-explicit converting move-construct from optional - template< typename U - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && std::is_convertible< U &&, T>::value /*=> non-explicit */ - ) - > - // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) - /*non-explicit*/ optional( optional && other ) - : has_value_( other.has_value() ) - { - if ( other.has_value() ) - { - contained.construct_value( std::move( other.contained.value() ) ); - } - } - - // 6 (C++11) - in-place construct - template< typename... Args - optional_REQUIRES_T( - std::is_constructible::value - ) - > - optional_constexpr explicit optional( nonstd_lite_in_place_t(T), Args&&... args ) - : has_value_( true ) - , contained( in_place, std::forward(args)... ) - {} - - // 7 (C++11) - in-place construct, initializer-list - template< typename U, typename... Args - optional_REQUIRES_T( - std::is_constructible&, Args&&...>::value - ) - > - optional_constexpr explicit optional( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) - : has_value_( true ) - , contained( T( il, std::forward(args)...) ) - {} - - // 8a (C++11) - explicit move construct from value - template< typename U = T - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_same::type, nonstd_lite_in_place_t(U)>::value - && !std::is_same::type, optional>::value - && !std::is_convertible::value /*=> explicit */ - ) - > - optional_constexpr explicit optional( U && value ) - : has_value_( true ) - , contained( nonstd_lite_in_place(T), std::forward( value ) ) - {} - - // 8b (C++11) - non-explicit move construct from value - template< typename U = T - optional_REQUIRES_T( - std::is_constructible::value - && !std::is_same::type, nonstd_lite_in_place_t(U)>::value - && !std::is_same::type, optional>::value - && std::is_convertible::value /*=> non-explicit */ - ) - > - // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) - optional_constexpr /*non-explicit*/ optional( U && value ) - : has_value_( true ) - , contained( nonstd_lite_in_place(T), std::forward( value ) ) - {} - -#else // optional_CPP11_OR_GREATER - - // 8 (C++98) - optional( value_type const & value ) - : has_value_( true ) - , contained( value ) - {} - -#endif // optional_CPP11_OR_GREATER - - // x.x.3.2, destructor - - ~optional() - { - if ( has_value() ) - { - contained.destruct_value(); - } - } - - // x.x.3.3, assignment - - // 1 (C++98and later) - assign explicitly empty - optional & operator=( nullopt_t /*unused*/) optional_noexcept - { - reset(); - return *this; - } - - // 2 (C++98and later) - copy-assign from optional -#if optional_CPP11_OR_GREATER - // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) - optional_REQUIRES_R( - optional &, - true -// std::is_copy_constructible::value -// && std::is_copy_assignable::value - ) - operator=( optional const & other ) - noexcept( - std11::is_nothrow_move_assignable::value - && std11::is_nothrow_move_constructible::value - ) -#else - optional & operator=( optional const & other ) -#endif - { - if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } - else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( *other ); } - else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = *other; } - return *this; - } - -#if optional_CPP11_OR_GREATER - - // 3 (C++11) - move-assign from optional - // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) - optional_REQUIRES_R( - optional &, - true -// std11::is_move_constructible::value -// && std::is_move_assignable::value - ) - operator=( optional && other ) noexcept - { - if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } - else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std::move( *other ) ); } - else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = std::move( *other ); } - return *this; - } - - // 4 (C++11) - move-assign from value - template< typename U = T > - // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) - optional_REQUIRES_R( - optional &, - std::is_constructible::value - && std11::is_assignable::value - && !std::is_same::type, nonstd_lite_in_place_t(U)>::value - && !std::is_same::type, optional>::value - && !(std::is_scalar::value && std::is_same::type>::value) - ) - operator=( U && value ) - { - if ( has_value() ) - { - contained.value() = std::forward( value ); - } - else - { - initialize( T( std::forward( value ) ) ); - } - return *this; - } - -#else // optional_CPP11_OR_GREATER - - // 4 (C++98) - copy-assign from value - template< typename U /*= T*/ > - optional & operator=( U const & value ) - { - if ( has_value() ) contained.value() = value; - else initialize( T( value ) ); - return *this; - } - -#endif // optional_CPP11_OR_GREATER - - // 5 (C++98 and later) - converting copy-assign from optional - template< typename U > -#if optional_CPP11_OR_GREATER - // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) - optional_REQUIRES_R( - optional&, - std::is_constructible< T , U const &>::value - && std11::is_assignable< T&, U const &>::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && !std11::is_assignable< T&, optional & >::value - && !std11::is_assignable< T&, optional && >::value - && !std11::is_assignable< T&, optional const & >::value - && !std11::is_assignable< T&, optional const && >::value - ) -#else - optional& -#endif // optional_CPP11_OR_GREATER - operator=( optional const & other ) - { - return *this = optional( other ); - } - -#if optional_CPP11_OR_GREATER - - // 6 (C++11) - converting move-assign from optional - template< typename U > - // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) - optional_REQUIRES_R( - optional&, - std::is_constructible< T , U>::value - && std11::is_assignable< T&, U>::value - && !std::is_constructible & >::value - && !std::is_constructible && >::value - && !std::is_constructible const & >::value - && !std::is_constructible const && >::value - && !std::is_convertible< optional & , T>::value - && !std::is_convertible< optional && , T>::value - && !std::is_convertible< optional const & , T>::value - && !std::is_convertible< optional const &&, T>::value - && !std11::is_assignable< T&, optional & >::value - && !std11::is_assignable< T&, optional && >::value - && !std11::is_assignable< T&, optional const & >::value - && !std11::is_assignable< T&, optional const && >::value - ) - operator=( optional && other ) - { - return *this = optional( std::move( other ) ); - } - - // 7 (C++11) - emplace - template< typename... Args - optional_REQUIRES_T( - std::is_constructible::value - ) - > - T& emplace( Args&&... args ) - { - *this = nullopt; - contained.emplace( std::forward(args)... ); - has_value_ = true; - return contained.value(); - } - - // 8 (C++11) - emplace, initializer-list - template< typename U, typename... Args - optional_REQUIRES_T( - std::is_constructible&, Args&&...>::value - ) - > - T& emplace( std::initializer_list il, Args&&... args ) - { - *this = nullopt; - contained.emplace( il, std::forward(args)... ); - has_value_ = true; - return contained.value(); - } - -#endif // optional_CPP11_OR_GREATER - - // x.x.3.4, swap - - void swap( optional & other ) -#if optional_CPP11_OR_GREATER - noexcept( - std11::is_nothrow_move_constructible::value - && std17::is_nothrow_swappable::value - ) -#endif - { - using std::swap; - if ( (has_value() == true ) && (other.has_value() == true ) ) { swap( **this, *other ); } - else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std11::move(*other) ); other.reset(); } - else if ( (has_value() == true ) && (other.has_value() == false) ) { other.initialize( std11::move(**this) ); reset(); } - } - - // x.x.3.5, observers - - optional_constexpr value_type const * operator ->() const - { - return assert( has_value() ), - contained.value_ptr(); - } - - optional_constexpr14 value_type * operator ->() - { - return assert( has_value() ), - contained.value_ptr(); - } - - optional_constexpr value_type const & operator *() const optional_ref_qual - { - return assert( has_value() ), - contained.value(); - } - - optional_constexpr14 value_type & operator *() optional_ref_qual - { - return assert( has_value() ), - contained.value(); - } - -#if optional_HAVE( REF_QUALIFIER ) - - optional_constexpr value_type const && operator *() const optional_refref_qual - { - return std::move( **this ); - } - - optional_constexpr14 value_type && operator *() optional_refref_qual - { - return std::move( **this ); - } - -#endif - -#if optional_CPP11_OR_GREATER - optional_constexpr explicit operator bool() const optional_noexcept - { - return has_value(); - } -#else - optional_constexpr operator safe_bool() const optional_noexcept - { - return has_value() ? &optional::this_type_does_not_support_comparisons : 0; - } -#endif - - // NOLINTNEXTLINE( modernize-use-nodiscard ) - /*optional_nodiscard*/ optional_constexpr bool has_value() const optional_noexcept - { - return has_value_; - } - - // NOLINTNEXTLINE( modernize-use-nodiscard ) - /*optional_nodiscard*/ optional_constexpr14 value_type const & value() const optional_ref_qual - { -#if optional_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - { - throw bad_optional_access(); - } -#endif - return contained.value(); - } - - optional_constexpr14 value_type & value() optional_ref_qual - { -#if optional_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - { - throw bad_optional_access(); - } -#endif - return contained.value(); - } - -#if optional_HAVE( REF_QUALIFIER ) && ( !optional_COMPILER_GNUC_VERSION || optional_COMPILER_GNUC_VERSION >= 490 ) - - // NOLINTNEXTLINE( modernize-use-nodiscard ) - /*optional_nodiscard*/ optional_constexpr value_type const && value() const optional_refref_qual - { - return std::move( value() ); - } - - optional_constexpr14 value_type && value() optional_refref_qual - { - return std::move( value() ); - } - -#endif - -#if optional_HAVE( REF_QUALIFIER ) - - template< typename U > - optional_constexpr value_type value_or( U && v ) const optional_ref_qual - { - return has_value() ? contained.value() : static_cast(std::forward( v ) ); - } - - template< typename U > - optional_constexpr14 value_type value_or( U && v ) optional_refref_qual - { -#if optional_COMPILER_CLANG_VERSION - return has_value() ? /*std::move*/( contained.value() ) : static_cast(std::forward( v ) ); -#else - return has_value() ? std::move( contained.value() ) : static_cast(std::forward( v ) ); -#endif - } - -#else - - template< typename U > - optional_constexpr value_type value_or( U const & v ) const - { - return has_value() ? contained.value() : static_cast( v ); - } - -#endif // optional_CPP11_OR_GREATER - - // x.x.3.6, modifiers - - void reset() optional_noexcept - { - if ( has_value() ) - { - contained.destruct_value(); - } - - has_value_ = false; - } - -private: - void this_type_does_not_support_comparisons() const {} - - template< typename V > - void initialize( V const & value ) - { - assert( ! has_value() ); - contained.construct_value( value ); - has_value_ = true; - } - -#if optional_CPP11_OR_GREATER - template< typename V > - void initialize( V && value ) - { - assert( ! has_value() ); - contained.construct_value( std::move( value ) ); - has_value_ = true; - } - -#endif - -private: - bool has_value_; - detail::storage_t< value_type > contained; - -}; - -// Relational operators - -template< typename T, typename U > -inline optional_constexpr bool operator==( optional const & x, optional const & y ) -{ - return bool(x) != bool(y) ? false : !bool( x ) ? true : *x == *y; -} - -template< typename T, typename U > -inline optional_constexpr bool operator!=( optional const & x, optional const & y ) -{ - return !(x == y); -} - -template< typename T, typename U > -inline optional_constexpr bool operator<( optional const & x, optional const & y ) -{ - return (!y) ? false : (!x) ? true : *x < *y; -} - -template< typename T, typename U > -inline optional_constexpr bool operator>( optional const & x, optional const & y ) -{ - return (y < x); -} - -template< typename T, typename U > -inline optional_constexpr bool operator<=( optional const & x, optional const & y ) -{ - return !(y < x); -} - -template< typename T, typename U > -inline optional_constexpr bool operator>=( optional const & x, optional const & y ) -{ - return !(x < y); -} - -// Comparison with nullopt - -template< typename T > -inline optional_constexpr bool operator==( optional const & x, nullopt_t /*unused*/ ) optional_noexcept -{ - return (!x); -} - -template< typename T > -inline optional_constexpr bool operator==( nullopt_t /*unused*/, optional const & x ) optional_noexcept -{ - return (!x); -} - -template< typename T > -inline optional_constexpr bool operator!=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept -{ - return bool(x); -} - -template< typename T > -inline optional_constexpr bool operator!=( nullopt_t /*unused*/, optional const & x ) optional_noexcept -{ - return bool(x); -} - -template< typename T > -inline optional_constexpr bool operator<( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept -{ - return false; -} - -template< typename T > -inline optional_constexpr bool operator<( nullopt_t /*unused*/, optional const & x ) optional_noexcept -{ - return bool(x); -} - -template< typename T > -inline optional_constexpr bool operator<=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept -{ - return (!x); -} - -template< typename T > -inline optional_constexpr bool operator<=( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept -{ - return true; -} - -template< typename T > -inline optional_constexpr bool operator>( optional const & x, nullopt_t /*unused*/ ) optional_noexcept -{ - return bool(x); -} - -template< typename T > -inline optional_constexpr bool operator>( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept -{ - return false; -} - -template< typename T > -inline optional_constexpr bool operator>=( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept -{ - return true; -} - -template< typename T > -inline optional_constexpr bool operator>=( nullopt_t /*unused*/, optional const & x ) optional_noexcept -{ - return (!x); -} - -// Comparison with T - -template< typename T, typename U > -inline optional_constexpr bool operator==( optional const & x, U const & v ) -{ - return bool(x) ? *x == v : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator==( U const & v, optional const & x ) -{ - return bool(x) ? v == *x : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator!=( optional const & x, U const & v ) -{ - return bool(x) ? *x != v : true; -} - -template< typename T, typename U > -inline optional_constexpr bool operator!=( U const & v, optional const & x ) -{ - return bool(x) ? v != *x : true; -} - -template< typename T, typename U > -inline optional_constexpr bool operator<( optional const & x, U const & v ) -{ - return bool(x) ? *x < v : true; -} - -template< typename T, typename U > -inline optional_constexpr bool operator<( U const & v, optional const & x ) -{ - return bool(x) ? v < *x : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator<=( optional const & x, U const & v ) -{ - return bool(x) ? *x <= v : true; -} - -template< typename T, typename U > -inline optional_constexpr bool operator<=( U const & v, optional const & x ) -{ - return bool(x) ? v <= *x : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator>( optional const & x, U const & v ) -{ - return bool(x) ? *x > v : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator>( U const & v, optional const & x ) -{ - return bool(x) ? v > *x : true; -} - -template< typename T, typename U > -inline optional_constexpr bool operator>=( optional const & x, U const & v ) -{ - return bool(x) ? *x >= v : false; -} - -template< typename T, typename U > -inline optional_constexpr bool operator>=( U const & v, optional const & x ) -{ - return bool(x) ? v >= *x : true; -} - -// Specialized algorithms - -template< typename T -#if optional_CPP11_OR_GREATER - optional_REQUIRES_T( - std11::is_move_constructible::value - && std17::is_swappable::value ) -#endif -> -void swap( optional & x, optional & y ) -#if optional_CPP11_OR_GREATER - noexcept( noexcept( x.swap(y) ) ) -#endif -{ - x.swap( y ); -} - -#if optional_CPP11_OR_GREATER - -template< typename T > -optional_constexpr optional< typename std::decay::type > make_optional( T && value ) -{ - return optional< typename std::decay::type >( std::forward( value ) ); -} - -template< typename T, typename...Args > -optional_constexpr optional make_optional( Args&&... args ) -{ - return optional( nonstd_lite_in_place(T), std::forward(args)...); -} - -template< typename T, typename U, typename... Args > -optional_constexpr optional make_optional( std::initializer_list il, Args&&... args ) -{ - return optional( nonstd_lite_in_place(T), il, std::forward(args)...); -} - -#else - -template< typename T > -optional make_optional( T const & value ) -{ - return optional( value ); -} - -#endif // optional_CPP11_OR_GREATER - -} // namespace optional_lite - -using optional_lite::optional; -using optional_lite::nullopt_t; -using optional_lite::nullopt; - -#if ! optional_CONFIG_NO_EXCEPTIONS -using optional_lite::bad_optional_access; -#endif - -using optional_lite::make_optional; - -} // namespace nonstd - -#if optional_CPP11_OR_GREATER - -// specialize the std::hash algorithm: - -namespace std { - -template< class T > -struct hash< nonstd::optional > -{ -public: - std::size_t operator()( nonstd::optional const & v ) const optional_noexcept - { - return bool( v ) ? std::hash{}( *v ) : 0; - } -}; - -} //namespace std - -#endif // optional_CPP11_OR_GREATER - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#elif defined(_MSC_VER ) -# pragma warning( pop ) -#endif - -#endif // optional_USES_STD_OPTIONAL - -#endif // NONSTD_OPTIONAL_LITE_HPP diff --git a/Nakama/Source/NakamaCore/libnakama/android/arm64-v8a/libnakama-sdk.so b/Nakama/Source/NakamaCore/libnakama/android/arm64-v8a/libnakama-sdk.so deleted file mode 100755 index 29988410f..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/android/arm64-v8a/libnakama-sdk.so and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/android/armeabi-v7a/libnakama-sdk.so b/Nakama/Source/NakamaCore/libnakama/android/armeabi-v7a/libnakama-sdk.so deleted file mode 100755 index 6cd41eaec..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/android/armeabi-v7a/libnakama-sdk.so and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/android/x86_64/libnakama-sdk.so b/Nakama/Source/NakamaCore/libnakama/android/x86_64/libnakama-sdk.so deleted file mode 100755 index ea7684040..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/android/x86_64/libnakama-sdk.so and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/ios-arm64/libnakama-sdk.dylib b/Nakama/Source/NakamaCore/libnakama/ios-arm64/libnakama-sdk.dylib deleted file mode 100755 index 3bcce3980..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/ios-arm64/libnakama-sdk.dylib and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/linux-x64/libnakama-sdk.so b/Nakama/Source/NakamaCore/libnakama/linux-x64/libnakama-sdk.so deleted file mode 100644 index ba48c15e1..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/linux-x64/libnakama-sdk.so and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/macosx-arm64/libnakama-sdk.dylib b/Nakama/Source/NakamaCore/libnakama/macosx-arm64/libnakama-sdk.dylib deleted file mode 100755 index 19208fa2b..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/macosx-arm64/libnakama-sdk.dylib and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.dll b/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.dll deleted file mode 100644 index 8b029f60e..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.dll and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.lib b/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.lib deleted file mode 100644 index 74fe2c4a7..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.lib and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.pdb b/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.pdb deleted file mode 100644 index 2e2932fdd..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-arm64/Debug/nakama-sdk.pdb and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.dll b/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.dll deleted file mode 100644 index bc832e389..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.dll and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.lib b/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.lib deleted file mode 100644 index 74fe2c4a7..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-arm64/Release/nakama-sdk.lib and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.dll b/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.dll deleted file mode 100644 index 5e4fd6890..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.dll and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.lib b/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.lib deleted file mode 100644 index 33319d8f5..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.lib and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.pdb b/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.pdb deleted file mode 100644 index dc710dc7c..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-x64/Debug/nakama-sdk.pdb and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.dll b/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.dll deleted file mode 100644 index c3193fa2f..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.dll and /dev/null differ diff --git a/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.lib b/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.lib deleted file mode 100644 index 33319d8f5..000000000 Binary files a/Nakama/Source/NakamaCore/libnakama/win-x64/Release/nakama-sdk.lib and /dev/null differ diff --git a/Nakama/Source/NakamaTests/Private/NakamaTests.cpp b/Nakama/Source/NakamaTests/Private/NakamaTests.cpp deleted file mode 100644 index 555cabc8d..000000000 --- a/Nakama/Source/NakamaTests/Private/NakamaTests.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTests.h" -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE "FNakamaTestsModule" - -DEFINE_LOG_CATEGORY(LogNakamaTests); - - -void FNakamaTestsModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module -} - -void FNakamaTestsModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FNakamaTestsModule, NakamaTests) diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_AuthoritativeMatch.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_AuthoritativeMatch.cpp deleted file mode 100644 index 33bcb7300..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_AuthoritativeMatch.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Realtime/Test_AuthoritativeMatch.h" - -#include "NakamaLoggingMacros.h" - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AuthoritativeMatch, FNakamaAuthoritativeMatchTestBase, "Nakama.Base.Realtime.Matches.AuthoritativeMatch", NAKAMA_MODULE_TEST_MASK) -inline bool AuthoritativeMatch::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket 1 Connected")); - - // Setup Client 2 and connect - - Client2 = CreateClient(); - - auto Client2AuthenticateSuccess = [this] (UNakamaSession* Client2Session) - { - Session2 = Client2Session; - - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket 2 Connected")); - - auto RpcSuccessCallback = [this](const FNakamaRPC& Rpc) - { - FString MatchId = GetMatchIdFromJsonString(Rpc.Payload); - if(!MatchId.IsEmpty()) - { - auto JoinMatchSuccessCallback = [this](const FNakamaMatch& Match) - { - UE_LOG(LogTemp, Display, TEXT("Joined Match. MatchId: %s"), *Match.MatchId); - TestTrue("Authoritative Match Test Passed", !Match.MatchId.IsEmpty()); - StopTest(); - }; - - auto JoinMatchErrorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Match Error. Message: %s"), *Error.Message); - TestFalse("Authoritative Match Test Failed - Join Match Error", true); - StopTest(); - }; - - Socket2->JoinMatch(MatchId, {}, JoinMatchSuccessCallback, JoinMatchErrorCallback); - } - else - { - UE_LOG(LogTemp, Warning, TEXT("MatchId is Empty")); - TestFalse("MatchId is Empty", true); - StopTest(); - } - }; - - auto RpcErrorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("RPC Error. Message: %s"), *Error.Message); - TestFalse("Authoritative Match Test Failed - RPC Error", true); - StopTest(); - }; - - const FString JsonPayload = "{\"debug\": true, \"label\": \"TestAuthoritativeMatch\"}"; - Socket->RPC("clientrpc.create_authoritative_match", JsonPayload, RpcSuccessCallback, RpcErrorCallback); - }); - - Socket2->Connect(Session, true); - }; - - auto Client2AuthenticateError = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Client 2 Authentication Error. Message: %s"), *Error.Message); - TestFalse("Authoritative Match Test Failed - Client 2 Authentication Error", true); - StopTest(); - }; - - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthenticateSuccess, Client2AuthenticateError); - - }); - - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authoritative Match Auth Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -FString FNakamaAuthoritativeMatchTestBase::GetMatchIdFromJsonString(const FString& JsonString) const -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - FString MatchId; - if (JsonObject->TryGetStringField(TEXT("match_id"), MatchId)) - { - return MatchId; - } - } - - // Return an empty string if the parsing fails or if the "match_id" field is not present - return FString(); -} diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Chat.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Chat.cpp deleted file mode 100644 index 185817ec1..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Chat.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" - -// Join Chat -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(JoinChat, FNakamaTestBase, "Nakama.Base.Realtime.Chat.JoinChat", NAKAMA_MODULE_TEST_MASK) -inline bool JoinChat::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket connected")); - - // Join Chat - auto successCallback = [&](const FNakamaChannel& Channel) - { - UE_LOG(LogTemp, Display, TEXT("Joined Chat ID: %s"), *Channel.Id); - UE_LOG(LogTemp, Display, TEXT("Joined Chat Room: %s"), *Channel.RoomName); - TestTrue("Join Chat Test Passed", !Channel.Id.IsEmpty()); - StopTest(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Chat error. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Chat Test error.", true); - StopTest(); - }; - - Socket->JoinChat(TEXT("General"), ENakamaChannelType::ROOM, true, false, successCallback, errorCallback); - }); - - Socket->SetDisconnectCallback( [](const FNakamaDisconnectInfo& DisconnectInfo) - { - UE_LOG(LogTemp, Warning, TEXT("Socket disconnected!")); - UE_LOG(LogTemp, Warning, TEXT("Socket disconnected: %s"), *DisconnectInfo.Reason); - }); - - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - - -// Join Chat and Wirte Chat Message -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(JoinChatWriteMessage, FNakamaTestBase, "Nakama.Base.Realtime.Chat.JoinChatWriteMessage", NAKAMA_MODULE_TEST_MASK) -inline bool JoinChatWriteMessage::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket connected")); - - // Join Chat - auto successCallback = [&](const FNakamaChannel& Channel) - { - auto writeChatMessageSuccessCallback = [&](const FNakamaChannelMessageAck& ChannelMessageAck) - { - UE_LOG(LogTemp, Display, TEXT("Joined Chat ID: %s"), *ChannelMessageAck.ChannelId); - UE_LOG(LogTemp, Display, TEXT("Joined Chat Room: %s"), *ChannelMessageAck.RoomName); - TestTrue("Join Chat Test Passed", !ChannelMessageAck.ChannelId.IsEmpty()); - StopTest(); - }; - - auto writechatMessageErrorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Write Chat Message error. ErrorMessage: %s"), *Error.Message); - TestFalse("Write Chat Message Test error.", true); - StopTest(); - }; - - Socket->WriteChatMessage( Channel.Id, TEXT("{ \"Hello\" : \"World\" }"), writeChatMessageSuccessCallback, writechatMessageErrorCallback ); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Chat error. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Chat Test error.", true); - StopTest(); - }; - - Socket->JoinChat(TEXT("General"), ENakamaChannelType::ROOM, true, false, successCallback, errorCallback); - }); - - Socket->SetDisconnectCallback( [](const FNakamaDisconnectInfo& DisconnectInfo) - { - UE_LOG(LogTemp, Warning, TEXT("Socket disconnected!")); - UE_LOG(LogTemp, Warning, TEXT("Socket disconnected: %s"), *DisconnectInfo.Reason); - }); - - Socket->SetChannelMessageCallback( [](const FNakamaChannelMessage& ChannelMessage) - { - UE_LOG(LogTemp, Warning, TEXT("Channel Message: %s"), *ChannelMessage.Content); - }); - - Socket->SetNotificationsCallback( [](const FNakamaNotificationList& NotificationList) - { - UE_LOG(LogTemp, Warning, TEXT("Notifications: %d"), NotificationList.Notifications.Num()); - for (auto& Notification : NotificationList.Notifications) - { - UE_LOG(LogTemp, Warning, TEXT("Notification: %s"), *Notification.Content); - } - }); - - Socket->SetStatusPresenceCallback( [](const FNakamaStatusPresenceEvent& StatusPresenceEvent) - { - UE_LOG(LogTemp, Warning, TEXT("Status Presences: %d"), StatusPresenceEvent.Joins.Num()); - for (auto& Presence : StatusPresenceEvent.Joins) - { - UE_LOG(LogTemp, Warning, TEXT("Joined Presence: %s"), *Presence.UserID); - } - }); - - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Join Chat -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(JoinGroupChat, FNakamaTestBase, "Nakama.Base.Realtime.Chat.JoinGroupChat", NAKAMA_MODULE_TEST_MASK) -inline bool JoinGroupChat::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket:S - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket connected")); - - auto CreateGroupSuccessCallback = [&](const FNakamaGroup& Group) - { - UE_LOG(LogTemp, Warning, TEXT("Group created: %s"), *Group.Id); - - // Join the group chat - - auto JoinGroupSuccessCallback = [&](const FNakamaChannel& Channel) - { - UE_LOG(LogTemp, Warning, TEXT("Joined Group Chat ID: %s"), *Channel.Id); - TestTrue("Join Group Chat Test Passed", !Channel.Id.IsEmpty()); - StopTest(); - }; - - auto JoinGroupErrorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Group Chat Error - Join Group. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Group Chat Error - Join Group.", true); - StopTest(); - }; - - Socket->JoinChat(Group.Id, ENakamaChannelType::GROUP, {}, {}, JoinGroupSuccessCallback, JoinGroupErrorCallback); - }; - - auto CreateGroupErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Group Chat Error - Create Group. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Group Chat Error - Create Group.", true); - StopTest(); - }; - - FString GroupName = FString::Printf(TEXT("Group chat %s"), *Session->GetAuthToken()); - Client->CreateGroup(Session, GroupName, TEXT("A group for chatting"), {}, {}, false, {}, CreateGroupSuccessCallback, CreateGroupErrorCallback); - }); - - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_FollowUsers.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_FollowUsers.cpp deleted file mode 100644 index 750c6ee8e..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_FollowUsers.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Realtime/Test_FollowUsers.h" -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" - -// Add Matchmaker, wait for Match Matched and Join Match by Token -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FollowUsers, FNakamaFollowUsersTestBase, "Nakama.Base.Realtime." - ".FollowUsers", NAKAMA_MODULE_TEST_MASK) -inline bool FollowUsers::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - Client2 = CreateClient(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([&]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Client 2 - auto Client2AuthSuccessCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session2 = session; - - // Setup socket: - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([&]() - { - auto successCallback = [&](const FNakamaStatus& Status) - { - TestTrue("FollowUsers test success", Status.Presences.Num() == 1); - StopTest(); - }; - - auto errorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("FollowUsers. ErrorMessage: %s"), *Error.Message); - TestFalse("FollowUsers Test error.", true); - StopTest(); - }; - - Socket->FollowUsers({Session2->GetUserId()}, successCallback, errorCallback); - }); - - // Connect with Socket - Socket2->Connect(Session2, true); // NOTE: This must use Session2 - }; - - auto Client2AuthErrorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthSuccessCallback, Client2AuthErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the Authenticate function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Match.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Match.cpp deleted file mode 100644 index ef6b7bb36..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Match.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Realtime/Test_Match.h" -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" - -// Create Match -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(CreateMatch, FNakamaTestBase, "Nakama.Base.Realtime.Matches.Match", NAKAMA_MODULE_TEST_MASK) -inline bool CreateMatch::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket connected")); - - // Join Chat - auto successCallback = [&](const FNakamaMatch& Match) - { - UE_LOG(LogTemp, Display, TEXT("Created Match: %s"), *Match.MatchId); - UE_LOG(LogTemp, Display, TEXT("Created Match Self Id: %s"), *Match.Me.UserID); - TestTrue("Create Match Test Passed", !Match.MatchId.IsEmpty() && !Match.Me.UserID.IsEmpty()); - StopTest(); - }; - - auto errorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Match error. ErrorMessage: %s"), *Error.Message); - TestFalse("Create Match Test error.", true); - StopTest(); - }; - - Socket->CreateMatch(successCallback, errorCallback); - }); - - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Add Matchmaker, must return a TicketId -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AddMatchmaker, FNakamaTestBase, "Nakama.Base.Realtime.Matches" - ".AddMatchmaker", NAKAMA_MODULE_TEST_MASK) -inline bool AddMatchmaker::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([&]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Add Matchmaker Callbacks - auto successCallback = [&](const FNakamaMatchmakerTicket& Ticket) - { - UE_LOG(LogTemp, Display, TEXT("Added Matchmaker with Ticket: %s"), *Ticket.TicketId); - TestTrue("Add Matchmaker Test Passed", !Ticket.TicketId.IsEmpty()); - StopTest(); - }; - - auto errorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Add Matchmaker. ErrorMessage: %s"), *Error.Message); - TestFalse("Add Matchmaker Test error.", true); - StopTest(); - }; - - // Matchmaker Params - // Note: Query: player_level >= 10 does not work. See: https://heroiclabs.com/docs/nakama/concepts/multiplayer/query-syntax/ - // Doing * instead - TOptional MinCount = 2; - TOptional MaxCount = 4; - TOptional Query = FString("*"); - TMap StringProperties; - StringProperties.Add("region", "us"); - TMap NumericProperties; - NumericProperties.Add("game_mode", 1); - NumericProperties.Add("game_difficulty", 2); - TOptional CountMultiple = 2; - - Socket->AddMatchmaker(MinCount, MaxCount, Query, StringProperties, NumericProperties, CountMultiple, successCallback, errorCallback); - }); - - Socket->SetDisconnectCallback( [&](const FNakamaDisconnectInfo& DisconnectInfo) - { - UE_LOG(LogTemp, Warning, TEXT("Socket disconnected: %s"), *DisconnectInfo.Reason); - TestFalse("Add Matchmaker Test error, socket disconnected", true); - StopTest(); - }); - - // Connect to Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Add Matchmaker, wait for Match Matched and Join Match by Token -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(MatchmakerJoinMatch, FNakamaMatchTestBase, "Nakama.Base.Realtime.Matches" - ".MatchmakerJoinMatch", NAKAMA_MODULE_TEST_MASK) -inline bool MatchmakerJoinMatch::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - Client2 = CreateClient(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Add Matchmaker Callbacks - auto successCallback = [&](const FNakamaMatchmakerTicket& Ticket) - { - UE_LOG(LogTemp, Display, TEXT("Added Matchmaker with Ticket: %s"), *Ticket.TicketId); - }; - - auto errorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Add Matchmaker. ErrorMessage: %s"), *Error.Message); - TestFalse("Add Matchmaker Test error.", true); - StopTest(); - }; - - Socket->AddMatchmaker(MinCount, MaxCount, Query, StringProperties, NumericProperties, CountMultiple, successCallback, errorCallback); - }); - - Socket->SetMatchmakerMatchedCallback( [&](const FNakamaMatchmakerMatched& MatchmakerMatched) - { - // Join Match by Token - UE_LOG( LogTemp, Warning, TEXT( "Socket Matchmaker Matched" ) ); - auto JoinMatchSuccessCallback = [&](const FNakamaMatch& Match) - { - UE_LOG(LogTemp, Display, TEXT("Joined Match: %s"), *Match.MatchId); - UE_LOG(LogTemp, Display, TEXT("Joined Match Self Id: %s"), *Match.Me.UserID); - TestTrue("Join Match by Token Test Passed", !Match.MatchId.IsEmpty() && !Match.Me.UserID.IsEmpty()); - StopTest(); - }; - - auto JoinMatchErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Join Match by Token error. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Match by Token Test error.", true); - StopTest(); - }; - - Socket->JoinMatchByToken(MatchmakerMatched.Token, JoinMatchSuccessCallback, JoinMatchErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the Authenticate function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - - // Client 2 - auto Client2AuthSuccessCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session2 = session; - - // Setup socket: - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 2 connected")); - - // Add Matchmaker Callbacks - auto successCallback = [&](const FNakamaMatchmakerTicket& Ticket) - { - UE_LOG(LogTemp, Display, TEXT("Added Matchmaker with Ticket: %s"), *Ticket.TicketId); - }; - - auto errorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Add Matchmaker. ErrorMessage: %s"), *Error.Message); - TestFalse("Add Matchmaker Test error.", true); - StopTest(); - }; - - // Matchmaker Params - - Socket2->AddMatchmaker(MinCount, MaxCount, Query, StringProperties, NumericProperties, CountMultiple, successCallback, errorCallback); - }); - - Socket2->SetMatchmakerMatchedCallback( [&](const FNakamaMatchmakerMatched& MatchmakerMatched) - { - UE_LOG( LogTemp, Warning, TEXT( "Socket 2 Matchmaker Matched" ) ); - // Join Match by Token - auto JoinMatchSuccessCallback = [&](const FNakamaMatch& Match) - { - UE_LOG(LogTemp, Display, TEXT("Joined Match: %s"), *Match.MatchId); - UE_LOG(LogTemp, Display, TEXT("Joined Match Self Id: %s"), *Match.Me.UserID); - TestTrue("Join Match by Token Test Passed", !Match.MatchId.IsEmpty() && !Match.Me.UserID.IsEmpty()); - StopTest(); - }; - - auto JoinMatchErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Join Match by Token error. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Match by Token Test error.", true); - StopTest(); - }; - - Socket2->JoinMatchByToken(MatchmakerMatched.Token, JoinMatchSuccessCallback, JoinMatchErrorCallback); - }); - - // Connect with Socket - Socket2->Connect(Session2, true); // NOTE: This must use Session2 - }; - - auto Client2AuthErrorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthSuccessCallback, Client2AuthErrorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Notifications.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Notifications.cpp deleted file mode 100644 index 8d0731399..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Notifications.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" - -// Send Notification to self from RPC and receive it -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(NotificationsCreateReceive, FNakamaTestBase, "Nakama.Base.Realtime.Notifications.CreateReceive", NAKAMA_MODULE_TEST_MASK) -inline bool NotificationsCreateReceive::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Notification RPC - auto RPCSuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Sent RPC: %s"), *RPC.Id); - }; - - auto RPCErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Notification Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPC error.", true); - StopTest(); - }; - - FString Payload = FString::Printf(TEXT("{\"user_id\":\"%s\"}"), *Session->GetUserId()); - - Socket->RPC("clientrpc.send_notification", Payload, RPCSuccessCallback, RPCErrorCallback); - }); - - Socket->SetNotificationsCallback( [&](const FNakamaNotificationList& NotificationList) - { - UE_LOG (LogTemp, Warning, TEXT("Notification Received: %d"), NotificationList.Notifications.Num()); - - for (auto Notification : NotificationList.Notifications) - { - UE_LOG (LogTemp, Warning, TEXT("Notification ID: %s"), *Notification.Id); - UE_LOG (LogTemp, Warning, TEXT("Notification Subject: %s"), *Notification.Subject); - UE_LOG (LogTemp, Warning, TEXT("Notification Content: %s"), *Notification.Content); - UE_LOG (LogTemp, Warning, TEXT("Notification Code: %d"), Notification.Code); - UE_LOG (LogTemp, Warning, TEXT("Notification SenderId: %s"), *Notification.SenderId); - UE_LOG (LogTemp, Warning, TEXT("Notification CreateTime: %s"), *Notification.CreateTime.ToString()); - UE_LOG (LogTemp, Warning, TEXT("Notification Persistent: %d"), Notification.Persistent); - } - - TestTrue("Notifications Test Passed", NotificationList.Notifications.Num() > 0); - StopTest(); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - - -// Send Notification to self from RPC and receive it -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(NotificationsCreateListDelete, FNakamaTestBase, "Nakama.Base.Realtime.Notifications.CreateListDelete", NAKAMA_MODULE_TEST_MASK) -inline bool NotificationsCreateListDelete::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Notification RPC - auto RPCSuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Sent RPC: %s"), *RPC.Id); - - // List Notifications - auto ListNotificationsSuccessCallback = [this](const FNakamaNotificationList& NotificationList) - { - if(NotificationList.Notifications.Num() > 0) - { - for (auto Notification : NotificationList.Notifications) - { - UE_LOG (LogTemp, Display, TEXT("Notification ID: %s"), *Notification.Id); - UE_LOG (LogTemp, Display, TEXT("Notification Code: %d"), Notification.Code); - UE_LOG (LogTemp, Display, TEXT("Notification Content: %s"), *Notification.Content); - - auto DeleteNotificationsSuccessCallback = [this, Notification]() - { - UE_LOG(LogTemp, Display, TEXT("Deleted Notification ID: %s"), *Notification.Id); - TestTrue("Notifications Test Passed", true); - StopTest(); - }; - - auto DeleteNotificationsErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("Delete Notifications Error. ErrorMessage: %s"), *Error.Message); - TestFalse("Delete Notifications Error.", true); - StopTest(); - }; - - // NOTE: Could also add Id's to an array and delete them all at once - Client->DeleteNotifications(Session, {Notification.Id}, DeleteNotificationsSuccessCallback, DeleteNotificationsErrorCallback); - } - } - else - { - UE_LOG(LogTemp, Display, TEXT("No Notifications Received")); - TestFalse("No Notifications Received", true); - StopTest(); - } - }; - - auto ListNotificationsErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("List Notifications Error. ErrorMessage: %s"), *Error.Message); - TestFalse("List Notifications Error.", true); - StopTest(); - }; - - Client->ListNotifications(Session, {}, {}, ListNotificationsSuccessCallback, ListNotificationsErrorCallback); - }; - - auto RPCErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Notification Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPC error.", true); - StopTest(); - }; - - FString Payload = FString::Printf(TEXT("{\"user_id\":\"%s\"}"), *Session->GetUserId()); - - Socket->RPC("clientrpc.send_notification", Payload, RPCSuccessCallback, RPCErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Parties.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Parties.cpp deleted file mode 100644 index 38507f3e4..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Parties.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Realtime/Test_Parties.h" - -// Create Party and Join Party Test Case -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(CreateParty, FNakamaPartiesTestBase, "Nakama.Base.Realtime.Parties.CreateParty", NAKAMA_MODULE_TEST_MASK) -inline bool CreateParty::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 1 connected")); - - auto CreatePartySuccessCallback = [&](const FNakamaParty& CreateParty) - { - Party = CreateParty; - SetupClient2AndJoinParty(); - }; - - auto CreatePartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Party. ErrorMessage: %s"), *Error.Message); - TestFalse("Create Party Test error.", true); - StopTest(); - }; - - Socket->CreateParty(true, 2, CreatePartySuccessCallback, CreatePartyErrorCallback); - }); - - Socket->SetPartyDataCallback( [&](const FNakamaPartyData& PartyData) - { - UE_LOG(LogTemp, Warning, TEXT("Party Data Callback. Data: %s"), *PartyData.Data); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -void FNakamaPartiesTestBase::SetupClient2AndJoinParty() -{ - Client2 = CreateClient(); - - auto Client2AuthSuccessCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session2 = session; - - // Setup socket: - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 2 connected")); - - // Join Party Callbacks - auto JoinPartySuccessCallback = [&]() - { - UE_LOG(LogTemp, Display, TEXT("Joined Party with Id: %s"), *Party.PartyId); - - Socket2->SendPartyData(Party.PartyId, 100, "Testing if it works"); - TestTrue("Create Party Test Passed", true); - StopTest(); - }; - - auto JoinPartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Join Party. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Party Test error.", true); - StopTest(); - }; - - Socket2->JoinParty(Party.PartyId, JoinPartySuccessCallback, JoinPartyErrorCallback); - }); - - Socket2->SetPartyCallback( [&](const FNakamaParty& MyParty) - { - UE_LOG(LogTemp, Warning, TEXT("Party Callback. PartyId: %s"), *MyParty.PartyId); - for (auto& Presence : MyParty.Presences) - { - UE_LOG(LogTemp, Display, TEXT("Event Presence: %s"), *Presence.UserID); - } - }); - - // Connect with Socket - Socket2->Connect(Session2, true); - }; - - auto Client2AuthErrorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthSuccessCallback, Client2AuthErrorCallback); -} - -void FNakamaPartiesTestBase::SetupClient2AndRequestJoinParty() -{ - Client2 = CreateClient(); - - auto Client2AuthSuccessCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session2 = session; - - // Setup socket: - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([&]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 2 connected")); - - // Join Party Callbacks - auto JoinPartySuccessCallback = [&]() - { - auto ListPartyJoinRequestsSuccessCallback = [&](const FNakamaPartyJoinRequest& JoinRequests) - { - TestTrue("List Party Join Requests Test Passed", JoinRequests.Presences.Num() == 1); - StopTest(); - }; - - auto ListPartyJoinRequestsErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Party Join Requests. ErrorMessage: %s"), *Error.Message); - TestFalse("List Party Join Requests Test error.", true); - StopTest(); - }; - - this->Socket->ListPartyJoinRequests(Party.PartyId, ListPartyJoinRequestsSuccessCallback, ListPartyJoinRequestsErrorCallback); - }; - - auto JoinPartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Join Party. ErrorMessage: %s"), *Error.Message); - TestFalse("List Party Join Requests Test error.", true); - StopTest(); - }; - - Socket2->JoinParty(Party.PartyId, JoinPartySuccessCallback, JoinPartyErrorCallback); - }); - - Socket2->SetPartyCallback([&](const FNakamaParty& MyParty) - { - UE_LOG(LogTemp, Warning, TEXT("Party Callback. PartyId: %s"), *MyParty.PartyId); - for (auto& Presence : MyParty.Presences) - { - UE_LOG(LogTemp, Display, TEXT("Event Presence: %s"), *Presence.UserID); - } - }); - - // Connect with Socket - Socket2->Connect(Session2, true); - }; - - auto Client2AuthErrorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthSuccessCallback, Client2AuthErrorCallback); -} - -void FNakamaPartiesTestBase::SetupClient2AndReceiveRequestJoinParty() -{ - Client2 = CreateClient(); - - auto Client2AuthSuccessCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session2 = session; - - // Setup socket: - Socket2 = Client2->SetupRealtimeClient(); - - Socket2->SetConnectCallback([&]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 2 connected")); - - // Join Party Callbacks - auto JoinPartySuccessCallback = [&]() {}; - - auto JoinPartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Join Party. ErrorMessage: %s"), *Error.Message); - TestFalse("List Party Join Requests Test error.", true); - StopTest(); - }; - - Socket2->JoinParty(Party.PartyId, JoinPartySuccessCallback, JoinPartyErrorCallback); - }); - - Socket2->SetPartyCallback([&](const FNakamaParty& MyParty) - { - UE_LOG(LogTemp, Warning, TEXT("Party Callback. PartyId: %s"), *MyParty.PartyId); - for (auto& Presence : MyParty.Presences) - { - UE_LOG(LogTemp, Display, TEXT("Event Presence: %s"), *Presence.UserID); - } - }); - - // Connect with Socket - Socket2->Connect(Session2, true); - }; - - auto Client2AuthErrorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - Client2->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, Client2AuthSuccessCallback, Client2AuthErrorCallback); -} - -// Create Party and Join Party Test Case -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(PartyMatchmaker, FNakamaPartiesTestBase, "Nakama.Base.Realtime.Parties.PartyMatchmaker", NAKAMA_MODULE_TEST_MASK) -inline bool PartyMatchmaker::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 1 connected")); - - auto CreatePartySuccessCallback = [&](const FNakamaParty& CreateParty) - { - Party = CreateParty; - //SetupClient2AndJoinParty(); - - auto AddMatchmakerPartySuccessCallback = [&](const FNakamaPartyMatchmakerTicket& Ticket) - { - UE_LOG(LogTemp, Warning, TEXT("Add Matchmaker Party Success Callback. Ticket: %s"), *Ticket.Ticket); - TestTrue( "Add Matchmaker Party Success", !Ticket.Ticket.IsEmpty()); - StopTest(); - }; - - auto AddMatchmakerPartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Add Matchmaker Party Error Callback. ErrorMessage: %s"), *Error.Message); - TestFalse("Add Matchmaker Party Error", true); - }; - - TOptional MinCount = 2; - TOptional MaxCount = 2; - TOptional Query(TEXT("*")); - - Socket->AddMatchmakerParty(Party.PartyId, MinCount, MaxCount, Query, {}, {} , {},AddMatchmakerPartySuccessCallback, AddMatchmakerPartyErrorCallback); - }; - - auto CreatePartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Party. ErrorMessage: %s"), *Error.Message); - TestFalse("Create Party Test error.", true); - StopTest(); - }; - - Socket->CreateParty(false, 1, CreatePartySuccessCallback, CreatePartyErrorCallback); - }); - - Socket->SetPartyDataCallback( [&](const FNakamaPartyData& PartyData) - { - UE_LOG(LogTemp, Warning, TEXT("Party Data Callback. Data: %s"), *PartyData.Data); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Create Party and Join Party Test Case -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ListPartyJoinRequests, FNakamaPartiesTestBase, "Nakama.Base.Realtime.Parties.ListPartyJoinRequests", NAKAMA_MODULE_TEST_MASK) -inline bool ListPartyJoinRequests::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 1 connected")); - - auto CreatePartySuccessCallback = [this](const FNakamaParty& CreateParty) - { - Party = CreateParty; - SetupClient2AndRequestJoinParty(); - }; - - auto CreatePartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Party. ErrorMessage: %s"), *Error.Message); - TestFalse("Create Party Test error.", true); - StopTest(); - }; - - Socket->CreateParty(false, 2, CreatePartySuccessCallback, CreatePartyErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Received Party Join Requests Test Case -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ReceivedPartyJoinRequests, FNakamaPartiesTestBase, "Nakama.Base.Realtime.Parties.ReceivedPartyJoinRequests", NAKAMA_MODULE_TEST_MASK) -inline bool ReceivedPartyJoinRequests::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetPartyJoinRequestCallback([this](const FNakamaPartyJoinRequest& JoinRequests) - { - TestTrue("List Party Join Requests Test Passed", JoinRequests.Presences.Num() == 1); - StopTest(); - }); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket 1 connected")); - - auto CreatePartySuccessCallback = [this](const FNakamaParty& CreateParty) - { - Party = CreateParty; - SetupClient2AndReceiveRequestJoinParty(); - }; - - auto CreatePartyErrorCallback = [&](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Party. ErrorMessage: %s"), *Error.Message); - TestFalse("Create Party Test error.", true); - StopTest(); - }; - - Socket->CreateParty(false, 2, CreatePartySuccessCallback, CreatePartyErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_RPC.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_RPC.cpp deleted file mode 100644 index adfad2dab..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_RPC.cpp +++ /dev/null @@ -1,431 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithHttpKey, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithHttpKey", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithHttpKey::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Notification RPC - auto RPCSuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Sent RPC with Payload: %s"), *RPC.Payload); - - if(!RPC.Payload.IsEmpty()) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithHttpKey Test Passed - Payload: %s"), *RPC.Payload); - TestTrue("RPCWithHttpKey Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithHttpKey Test Failed")); - TestFalse("RPCWithHttpKey Test Failed", true); - StopTest(); - } - }; - - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithHttpKey Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithHttpKey error.", true); - StopTest(); - }; - - const FString FunctionId = "clientrpc.rpc"; - const FString Payload = "{\"v\":\"test\"}"; - - // Test 1: With Payload - Client->RPC(ServerHttpKey, FunctionId, Payload, RPCSuccessCallback, RPCErrorCallback); - - // Test 2: Without Payload - //Client->RPC(ServerHttpKey, FunctionId, {}, RPCSuccessCallback, RPCErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPCWithHttpKey Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithHttpKey2, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithHttpKey2", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithHttpKey2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Socket connected")); - - // Notification RPC - auto RPCSuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Sent RPC: %s"), *RPC.Id); - - // This test is flipped because it does not send payload - if(RPC.Payload.IsEmpty()) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithHttpKey Test Passed - Payload: %s"), *RPC.Payload); - TestTrue("RPCWithHttpKey Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithHttpKey Test Failed")); - TestFalse("RPCWithHttpKey Test Failed", true); - StopTest(); - } - }; - - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithHttpKey Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithHttpKey error.", true); - StopTest(); - }; - - const FString FunctionId = "clientrpc.rpc"; - const FString Payload = "{\"v\":\"test\"}"; - - // Test 1: With Payload - //Client->RPC(ServerHttpKey, FunctionId, Payload, RPCSuccessCallback, RPCErrorCallback); - - // Test 2: Without Payload - Client->RPC(ServerHttpKey, FunctionId, {}, RPCSuccessCallback, RPCErrorCallback); - }); - - // Connect with Socket - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPCWithHttpKey Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithAuth1, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithAuth1", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithAuth1::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // RPCs - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithAuth error.", true); - StopTest(); - }; - - // Test 1 - auto RPC1SuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Sent RPC: %s"), *RPC.Payload ); - - // We expect empty payload - if(RPC.Payload.IsEmpty()) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithAuth Test Passed")); - TestTrue("RPCWithAuth Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test Failed")); - TestFalse("RPCWithAuth Test Failed", true); - StopTest(); - } - }; - - Client->RPC(Session, "clientrpc.rpc", {}, RPC1SuccessCallback, RPCErrorCallback); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPC with Auth Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithAuth2, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithAuth2", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithAuth2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // RPCs - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithAuth error.", true); - StopTest(); - }; - - // Test 2 - FString Json = FString::Printf(TEXT("{\"user_id\":\"%s\"}"), *Session->GetUserId()); - auto RPC2SuccessCallback = [&,Json](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Payload: %s"), *RPC.Payload); - UE_LOG(LogTemp, Display, TEXT("Json: %s"), *Json); - - // We expect User ID in payload - if(RPC.Payload == Json) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithAuth Test Passed")); - TestTrue("RPCWithAuth Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test Failed")); - TestFalse("RPCWithAuth Test Failed", true); - StopTest(); - } - }; - - Client->RPC(Session, "clientrpc.rpc", Json, RPC2SuccessCallback, RPCErrorCallback); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPC with Auth Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithAuth4, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithAuth4", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithAuth4::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // RPCs - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithAuth error.", true); - StopTest(); - }; - - // Test 4 - auto RPC4SuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Payload: %s"), *RPC.Payload); - - // We expect non-empty payload - if(!RPC.Payload.IsEmpty()) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithAuth Test Passed")); - TestTrue("RPCWithAuth Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test Failed")); - TestFalse("RPCWithAuth Test Failed", true); - StopTest(); - } - }; - - const FString Payload = "{}"; - Client->RPC(Session, "clientrpc.rpc", Payload, RPC4SuccessCallback, RPCErrorCallback); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPC with Auth Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RPCWithAuth5, FNakamaTestBase, "Nakama.Base.Realtime.RPC.RPCWithAuth5", NAKAMA_MODULE_TEST_MASK) -inline bool RPCWithAuth5::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // RPCs - auto RPCErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test. ErrorMessage: %s"), *Error.Message); - TestFalse("RPCWithAuth error.", true); - StopTest(); - }; - - // Test 5 - FString JsonRPC5 = FString::Printf(TEXT("{\"user_id\":\"%s\"}"), *Session->GetUserId()); - auto RPC5SuccessCallback = [&](const FNakamaRPC& RPC) - { - UE_LOG(LogTemp, Display, TEXT("Payload: %s"), *RPC.Payload); - - // We expect non-empty payload - if(!RPC.Payload.IsEmpty()) - { - UE_LOG(LogTemp, Display, TEXT("RPCWithAuth Test Passed")); - TestTrue("RPCWithAuth Test Passed", true); - StopTest(); - } - else - { - UE_LOG(LogTemp, Error, TEXT("RPCWithAuth Test Failed")); - TestFalse("RPCWithAuth Test Failed", true); - StopTest(); - } - }; - - Client->RPC(Session, "clientrpc.rpc", JsonRPC5, RPC5SuccessCallback, RPCErrorCallback); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("RPC with Auth Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Tournament.cpp b/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Tournament.cpp deleted file mode 100644 index bc0f59b30..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Realtime/Test_Tournament.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Realtime/Test_Tournament.h" - -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(Tournament, FNakamaTournamentTestBase, "Nakama.Base.Realtime.Tournament", NAKAMA_MODULE_TEST_MASK) -inline bool Tournament::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Setup socket: - Socket = Client->SetupRealtimeClient(); - - Socket->SetConnectCallback([this]() - { - UE_LOG(LogTemp, Warning, TEXT("Test Socket connected")); - - // Create a JSON object and add properties to it - TSharedPtr JsonObject = MakeShareable(new FJsonObject); - - JsonObject->SetBoolField("authoritative", true); - JsonObject->SetStringField("sort_order", "desc"); - JsonObject->SetStringField("operator", Operator); - JsonObject->SetNumberField("duration", Duration); - JsonObject->SetStringField("reset_schedule", ResetSchedule); - JsonObject->SetStringField("title", "Daily Dash"); - JsonObject->SetStringField("description", "Dash past your opponents for high scores and big rewards!"); - JsonObject->SetNumberField("category", 1); - JsonObject->SetNumberField("start_time", StartTime); - JsonObject->SetNumberField("end_time", EndTime); - JsonObject->SetNumberField("max_size", MaxSize); - JsonObject->SetNumberField("max_num_score", MaxNumScore); - JsonObject->SetBoolField("join_required", JoinRequired); - - // Convert the JSON object to a string - FString JsonString; - TSharedRef> JsonWriter = TJsonWriterFactory::Create(&JsonString); - FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); - - auto CreateTournamentSuccessCallback = [&](const FNakamaRPC& Rpc) - { - UE_LOG(LogTemp, Warning, TEXT("Tournament created: %s"), *Rpc.Payload ); - - FString TournamentId = GetTournamentIdFromJsonString(Rpc.Payload); - if(!TournamentId.IsEmpty()) - { - // Join the tournament - auto JoinTournamentSuccessCallback = [&, TournamentId]() - { - UE_LOG(LogTemp, Warning, TEXT("Joined Tournament ID: %s"), *TournamentId); - TestTrue("Tournament Test Passed",true); - StopTest(); - }; - - auto JoinTournamentErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Tournament Error - Join Tournament. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Tournament Error - Join Tournament.", true); - StopTest(); - }; - - Client->JoinTournament(Session, TournamentId, JoinTournamentSuccessCallback, JoinTournamentErrorCallback); - } - else - { - UE_LOG(LogTemp, Warning, TEXT("TournamentId is Empty")); - TestFalse("TournamentId is Empty", true); - StopTest(); - } - }; - - auto CreateTournamentErrorCallback = [this](const FNakamaRtError& Error) - { - UE_LOG(LogTemp, Warning, TEXT("Join Tournament Error - Create Tournament. ErrorMessage: %s"), *Error.Message); - TestFalse("Join Tournament Error - Create Tournament.", true); - StopTest(); - }; - - Socket->RPC("clientrpc.create_tournament", JsonString, CreateTournamentSuccessCallback, CreateTournamentErrorCallback); - }); - - // In this test we use a custom external listener, instead of the one provided with the Realtime Client - Socket->Connect(Session, true); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Tournament Test Failed to Authenticate", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -FString FNakamaTournamentTestBase::GetTournamentIdFromJsonString(const FString& JsonString) const -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - FString TournamentId; - if (JsonObject->TryGetStringField(TEXT("tournament_id"), TournamentId)) - { - return TournamentId; - } - } - - // Return an empty string if the parsing fails or if the "match_id" field is not present - return FString(); -} diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Authentication.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Authentication.cpp deleted file mode 100644 index 24092a626..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Authentication.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" -#include "NakamaUtils.h" -#include "Misc/AutomationTest.h" - -// Authenticate Email -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AuthenticateEmail, FNakamaTestBase, "Nakama.Base.Authenticate.Email", NAKAMA_MODULE_TEST_MASK) -inline bool AuthenticateEmail::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Setup test variables - TMap InVars; - InVars.Add(TEXT("test1"), TEXT("testValue1")); - InVars.Add(TEXT("test2"), TEXT("testValue2")); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Test passes if the authentication succeeds - TestTrue("Authentication Test Passed", !Session->GetAuthToken().IsEmpty()); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateEmail("test@mail.com", "12345678", "", true, InVars, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Authenticate Email 2 -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AuthenticateEmail2, FNakamaTestBase, "Nakama.Base.Authenticate.Email2", NAKAMA_MODULE_TEST_MASK) -inline bool AuthenticateEmail2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Setup test variables - TMap InVars; - InVars.Add(TEXT("test1"), TEXT("testValue1")); - InVars.Add(TEXT("test2"), TEXT("testValue2")); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG( LogTemp, Warning, TEXT("Username: %s"), *Session->GetUsername()); - - // Test passes if the authentication succeeds - TestTrue("Authentication Test Passed", !Session->GetAuthToken().IsEmpty() && TEXT("βσκαταη1234") == Session->GetUsername()); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateEmail("test1234@mail.com", "12345678", TEXT("βσκαταη1234"), true, {}, successCallback, errorCallback); // has issues - //Client->AuthenticateEmail("test@mail.com", "12345678", "test3", true, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Authenticate Device -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AuthenticateDevice, FNakamaTestBase, "Nakama.Base.Authenticate.Device", NAKAMA_MODULE_TEST_MASK) -inline bool AuthenticateDevice::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Test passes if the authentication succeeds - TestTrue("Device Authentication Test Passed", !Session->GetAuthToken().IsEmpty()); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Device Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - TOptional bCreate = true; - Client->AuthenticateDevice("mytestdevice0000", {}, {}, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Authenticate Device2 -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(AuthenticateDevice2, FNakamaTestBase, "Nakama.Base.Authenticate.Device2", NAKAMA_MODULE_TEST_MASK) -inline bool AuthenticateDevice2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Setup test variables - TMap InVars; - InVars.Add(TEXT("param1"), TEXT("test value")); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Test passes if the authentication succeeds - TestTrue("Device Authentication Test Passed", !Session->GetAuthToken().IsEmpty() && Session->GetVariable("param1") == "test value"); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Device Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateDevice("mytestdevice0000", true, {}, InVars, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Errors.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Errors.cpp deleted file mode 100644 index e4cdd1ee4..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Errors.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" - -// Error Not Found -// TODO: Disabled because this Test Fails, returns PermissionDenied from server, expects NotFound -/*IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ErrorNotFound, FNakamaTestBase, "Nakama.Base.Errors.NotFound", NAKAMA_MODULE_TEST_MASK) -inline bool ErrorNotFound::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // We do not want test to pass if authenticated - TestFalse("Not Found Test Failed", true); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - UE_LOG( LogTemp, Warning, TEXT("Error Code: %d"), Error.Code ); - TestTrue("Not Found Test Passed", Error.Code == ENakamaErrorCode::NotFound); // Test fails as it returns PermissionDenied - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateDevice("_not_existing_device_id_", false, {}, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -}*/ - -// Error Invalid Argument -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ErrorInvalidArgument, FNakamaTestBase, "Nakama.Base.Errors.InvalidArgument", NAKAMA_MODULE_TEST_MASK) -inline bool ErrorInvalidArgument::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // We do not want test to pass if authenticated - TestFalse("Invalid Argument Test Failed", true); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - UE_LOG( LogTemp, Warning, TEXT("Error Code: %d"), Error.Code ); - TestTrue("Invalid Argument Test Passed", Error.Code == ENakamaErrorCode::InvalidArgument); - StopTest(); - }; - - Client->AuthenticateDevice("", false, {}, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Error Invalid Argument -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ErrorInvalidArgument2, FNakamaTestBase, "Nakama.Base.Errors.InvalidArgument2", NAKAMA_MODULE_TEST_MASK) -inline bool ErrorInvalidArgument2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // We do not want test to pass if authenticated - TestFalse("Invalid Argument 2 Test Failed", true); - StopTest(); - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - UE_LOG( LogTemp, Warning, TEXT("Error Code: %d"), Error.Code ); - TestTrue("Invalid Argument 2 Test Passed", Error.Code == ENakamaErrorCode::InvalidArgument); - StopTest(); - }; - - Client->AuthenticateDevice("1", false, {}, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Error Unauthenticated -// TODO: Disabled because this Test Fails, returns Error Code 16 from server, expects Unauthenticated (4) -/*IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ErrorUnauthenticated, FNakamaTestBase, "Nakama.Base.Errors.Unauthenticated", NAKAMA_MODULE_TEST_MASK) -inline bool ErrorUnauthenticated::RunTest(const FString& Parameters) -{ - // initiates the test - InitiateTest(); - - UNakamaSession* RestoredSession = UNakamaSession::RestoreSession("dfgdfgdfg.dfgdfgdfg.dfgdfgdfg", "dfgdfgdfg.dfgdfgdfg.dfgdfgdfg"); - - auto errorCallback = [this, RestoredSession](const FNakamaError& Error) - { - UE_LOG( LogTemp, Warning, TEXT("Error Code: %d"), Error.Code ); - UE_LOG( LogTemp, Warning, TEXT("Error Message: %s"), *Error.Message); - UE_LOG(LogTemp, Warning, TEXT("Session Token: %s"), *RestoredSession->GetAuthToken()); - - TestTrue("Unauthenticated Test Passed", Error.Code == ENakamaErrorCode::Unauthenticated); // Test Fails, returns Error Code 16 from server, expects Unauthenticated (4) - StopTest(); - }; - - Client->GetAccount(RestoredSession, {}, errorCallback); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -}*/ \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Friends.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Friends.cpp deleted file mode 100644 index 2d936420f..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Friends.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Test_Friends.h" - -#include "NakamaLogger.h" -#include "NakamaLoggingMacros.h" -#include "NakamaTestBase.h" - -// List friends and testing cursor -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ListFriends, FNakamaFriendsTestBase, "Nakama.Base.Friends.ListFriends", NAKAMA_MODULE_TEST_MASK) -inline bool ListFriends::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - auto AuthenticateSuccess = [&](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG( LogTemp, Display, TEXT("Session: %s"), *Session->GetAuthToken() ); - - for (int i = 0; i < FriendsToAdd; i++) - { - auto FriendAuthenticateSuccess = [&](UNakamaSession* friendSession) - { - FriendIds.Add(friendSession->GetUserId()); - - // Done with authentication of friends - if (FriendIds.Num() == FriendsToAdd) - { - // Add Friends - auto FriendsAddedSuccess = [&]() - { - auto ListFriendsSuccess = [&] (const FNakamaFriendList& Friends) - { - FNakamaFriendList InvitedList = Friends; - - if(InvitedList.NakamaUsers.Num() == 0) - { - NAKAMA_LOG_ERROR("empty invited list 1"); - TestFalse("empty invited list 1", true); - StopTest(); - } - - FString ReturnedFriendId1 = Friends.NakamaUsers[0].NakamaUser.Id; - - auto ListFriends2Success = [&] (const FNakamaFriendList& Friends2) - { - if(Friends2.NakamaUsers.Num() == 0) - { - NAKAMA_LOG_ERROR("Empty invited list 2"); - TestFalse("Empty invited list 2", true); - StopTest(); - } - - FString ReturnedFriendId2 = Friends2.NakamaUsers[0].NakamaUser.Id; - TestTrue("List Friends Test Passed", ReturnedFriendId1 != ReturnedFriendId2); - StopTest(); - }; - - auto ListFriends2Error = [&] (const FNakamaError& error) - { - // If this is reached then the cursor is most likely invalid - UE_LOG(LogTemp, Display, TEXT("ListFriends2Error: %s"), *FString(error.Message)); - TestFalse("ListFriends2Error", true); - StopTest(); - }; - - // Passing cursor into function - Client->ListFriends(Session, ListFriendsLimit, ENakamaFriendState::INVITE_SENT, InvitedList.Cursor, ListFriends2Success, ListFriends2Error); - - }; - - Client->ListFriends(Session, ListFriendsLimit, ENakamaFriendState::INVITE_SENT, "", ListFriendsSuccess, {}); - - }; - - // Test that using cursor gives a different friend. - Client->AddFriends(Session, FriendIds, {}, FriendsAddedSuccess, {}); - } - }; - - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, FriendAuthenticateSuccess, {}); - } - - }; - - // Define error callback - auto AuthenticateError = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("List Friends Test Failed", true); - StopTest(); - }; - - Client->AuthenticateCustom(FGuid::NewGuid().ToString(), "", true, {}, AuthenticateSuccess, AuthenticateError); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_GetAccount.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_GetAccount.cpp deleted file mode 100644 index bf07af00d..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_GetAccount.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" - -// Get Account -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(GetAccount, FNakamaTestBase, "Nakama.Base.Users.GetAccount", NAKAMA_MODULE_TEST_MASK) -inline bool GetAccount::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Setup test variables - TMap InVars; - InVars.Add(TEXT("test1"), TEXT("testValue1")); - InVars.Add(TEXT("test2"), TEXT("testValue2")); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - UE_LOG (LogTemp, Warning, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // Get Account: - auto successCallback = [&](const FNakamaAccount& Account) - { - UE_LOG(LogTemp, Warning, TEXT("Account ID: %s"), *Account.User.Id); - UE_LOG(LogTemp, Warning, TEXT("Account Created: %ls"), *Account.User.CreatedAt.ToString()); - - //TestTrue("Get Account Test Passed", !Account.User.Id.IsEmpty()); - //StopTest(); - - auto UpdateAccountSuccessCallback = [this]() - { - UE_LOG(LogTemp, Display, TEXT("Account Was Updated")); - TestTrue("Get Account Test Passed - Account was Upated", true); - StopTest(); - }; - - auto UpdateAccountErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Account retrieval error. ErrorMessage: %s"), *Error.Message); - TestFalse("Account update error.", true); - StopTest(); - }; - - const FString NewDisplayName = TEXT("Nakama-test"); - - Client->UpdateAccount( - Session, - {}, - NewDisplayName, // Update Display Name - {}, - {}, - {}, - {}, - UpdateAccountSuccessCallback, - UpdateAccountErrorCallback - ); - }; - - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Account retrieval error. ErrorMessage: %s"), *Error.Message); - TestFalse("Account retrieval error.", true); - StopTest(); - }; - - Client->GetAccount(Session, successCallback, errorCallback); - - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Get Account Test Failed: Authentication", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateEmail("account-test@example.com", "12345678", "get-account-test", true, InVars, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_GetUsers.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_GetUsers.cpp deleted file mode 100644 index ec4ba4f91..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_GetUsers.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" - -// Authenticate Email -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(GetUsers, FNakamaTestBase, "Nakama.Base.Users.GetUsers", NAKAMA_MODULE_TEST_MASK) -inline bool GetUsers::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Setup test variables - TMap InVars; - InVars.Add(TEXT("test1"), TEXT("testValue1")); - InVars.Add(TEXT("test2"), TEXT("testValue2")); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // Get Users: - auto successCallback = [&](const FNakamaUserList& Users) - { - //UE_LOG(LogTemp, Warning, TEXT("Account ID: %s"), *Account.User.Id); - //UE_LOG(LogTemp, Warning, TEXT("Account Created: %ls"), *Account.User.CreatedAt.ToString()); - UE_LOG(LogTemp, Display, TEXT("Get Users Success. Users: %d"), Users.Users.Num()); - - for (auto& User : Users.Users) - { - UE_LOG(LogTemp, Warning, TEXT("User ID: %s"), *User.Id); - UE_LOG(LogTemp, Warning, TEXT("UserName: %s"), *User.Username); - UE_LOG(LogTemp, Warning, TEXT("User Created: %ls"), *User.CreatedAt.ToString()); - } - - if(Users.Users.Num() <= 0) - { - TestFalse("Get Users Error. No Users Found", true); - return; - } - - TestTrue("Get Users Success. Users Test Passed", Users.Users.Num() >= 1 && !Users.Users[0].Id.IsEmpty() ); - StopTest(); - }; - - auto errorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Get Users Error. ErrorMessage: %s"), *Error.Message); - TestFalse("Get Users Error.", true); - StopTest(); - }; - - TArray UserIds; - //UserIds.Add("9f4218bf-a894-44bd-9d58-2ad18735fde6"); - //UserIds.Add("6613f4fa-2684-4859-a10d-854047c14b77"); - UserIds.Add(Session->GetUserId()); // Add Self - - Client->GetUsers(Session, UserIds, {}, {}, successCallback, errorCallback); - - }; - - // Define error callback - auto errorCallback = [&](const FNakamaError& Error) - { - // Test fails if there is an authentication error - TestFalse("Authentication Test Failed", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateEmail("account-test@example.com", "12345678", "get-account-test", true, InVars, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Groups.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Groups.cpp deleted file mode 100644 index 75d7e08c9..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Groups.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Test_Groups.h" -#include "Misc/AutomationTest.h" - -// List Groups -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ListGroups, FNakamaGroupsTestBase, "Nakama.Base.Groups.List", NAKAMA_MODULE_TEST_MASK) -inline bool ListGroups::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Auth Success - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - UE_LOG(LogTemp, Display, TEXT("Session Token: %s"), *Session->GetAuthToken()); - - // List Groups - DoListGroups(); - }; - - // Call the AuthenticateEmail functions - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, {}); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -void FNakamaGroupsTestBase::DoListGroups() -{ - auto successCallback = [this](const FNakamaGroupList& Groups) - { - UE_LOG(LogTemp, Display, TEXT("List Groups Success. Groups: %d"), Groups.Groups.Num()); - - if (Groups.Groups.Num() > 0) - { - // Group exists, update it - UpdateGroup(Groups.Groups[0].Id); - } - else - { - // Group does not exist, create it - CreateGroup(); - } - }; - - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Groups Error: %s"), *Error.Message); - TestFalse("List Groups Test Failed", true); - StopTest(); - }; - - Client->ListGroups(Session, GroupName, 0, "", successCallback, errorCallback); -} - -void FNakamaGroupsTestBase::CreateGroup() -{ - - auto successCallback = [this] (const FNakamaGroup& Group) - { - UpdateGroup(Group.Id); - }; - - auto errorCallback = [this] (const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Create Group Error: %s"), *Error.Message); - TestFalse("Create Group Test Failed", true); - StopTest(); - }; - - FString Description = TEXT("Nakama is cool!"); - - Client->CreateGroup( - Session, - GroupName, - Description, - "", // Avatar URL - "", - true, // Open - {}, - successCallback, - errorCallback - ); -} - -void FNakamaGroupsTestBase::UpdateGroup(const FString& GroupId) -{ - auto successCallback = [this, GroupId]() - { - UE_LOG(LogTemp, Display, TEXT("Group Updated. Group ID: %s"), *GroupId); - // Proceed to list group users - ListGroupUsers(GroupId); - }; - - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Update Group Error: %s"), *Error.Message); - TestFalse("Update Group Test Failed", true); - StopTest(); - }; - - FString Description = TEXT("Nakama is cool!"); - - Client->UpdateGroup( - Session, - GroupId, - {}, - Description, - {}, - {}, // LangTag - {}, - successCallback, - errorCallback - ); -} - -void FNakamaGroupsTestBase::ListGroupUsers(const FString& GroupId) -{ - auto successCallback = [this](const FNakamaGroupUsersList& GroupUsers) - { - UE_LOG(LogTemp, Display, TEXT("List Group Users Success. GroupUsers: %d"), GroupUsers.GroupUsers.Num()); - - if (GroupUsers.GroupUsers.Num() <= 0) - { - UE_LOG(LogTemp, Display, TEXT("List Group Users Error. No GroupUsers Found")); - TestFalse("List Group Users Error. No GroupUsers Found", true); - StopTest(); - return; - } - - TestTrue("List Group Users Test Passed", GroupUsers.GroupUsers.Num() >= 1 && !GroupUsers.GroupUsers[0].User.Id.IsEmpty()); - StopTest(); - }; - - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Group Users Error: %s"), *Error.Message); - TestFalse("List Group Users Test Failed", true); - StopTest(); - }; - - Client->ListGroupUsers(Session, GroupId, 30, ENakamaGroupState::SUPERADMIN, "", successCallback, errorCallback); -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Internals.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Internals.cpp deleted file mode 100644 index 32d37aec0..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Internals.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "GenericPlatform/GenericPlatformHttp.h" -#include "Misc/AutomationTest.h" - -IMPLEMENT_SIMPLE_AUTOMATION_TEST(Test_Internals, "Nakama.Base.Internals.UriEncode", - EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) - -// It is important to use TEXT macro, otherwise this test would fail! -bool Test_Internals::RunTest(const FString& Parameters) -{ - FString Input = TEXT("βσκαταη3"); - FString Expected = TEXT("%CE%B2%CF%83%CE%BA%CE%B1%CF%84%CE%B1%CE%B73"); - - const FString EncodedString = FGenericPlatformHttp::UrlEncode(Input); - - if( EncodedString != Expected ) - { - UE_LOG(LogTemp, Display, TEXT("EncodedString: %s"), *EncodedString); - UE_LOG(LogTemp, Display, TEXT("Expected: %s"), *Expected); - TestFalse("UriEncode Test Failed", true); - } - else - { - UE_LOG(LogTemp, Display, TEXT("EncodedString: %s"), *EncodedString); - UE_LOG(LogTemp, Display, TEXT("Expected: %s"), *Expected); - TestTrue("UriEncode Test Passed", true); - } - - // Make the test pass by returning true, or fail by returning false. - return true; -} diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_ListMatches.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_ListMatches.cpp deleted file mode 100644 index cfe987705..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_ListMatches.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" -#include "Misc/AutomationTest.h" -#include "Tests/AutomationCommon.h" - -// List Matches -// TODO: Disabled because this Test Fails, it only works with a delay after the RPC call, could be that matches are not created before RPC returns success -/*IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(ListMatches, FNakamaTestBase, "Nakama.Base.Matches.ListMatches", NAKAMA_MODULE_TEST_MASK) -inline bool ListMatches::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Auth Success - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - // List Matches - auto RPCSuccessCallback = [this](const FNakamaRPC& Rpc) - { - auto ListMatchesSuccessCallback = [this] (const FNakamaMatchList& MatchList) - { - UE_LOG(LogTemp, Display, TEXT("Expecting match count to be 2. Actual count: %d"), MatchList.Matches.Num()); - TestTrue("List Matches Test Passed", MatchList.Matches.Num() == 2); - StopTest(); - }; - - auto ListMatchesErrorCallback = [this] (const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Matches Error: %s"), *Error.Message); - TestFalse("List Matches Test Failed: List Matches Error", true); - StopTest(); - }; - - int MinPlayers = 0; - int MaxPlayers = 10; - int Limit = 10; - bool Authoritative = true; - FString Label = TEXT(""); - FString Query = TEXT("+label.type:freeforall +label.difficulty:>1"); - - Client->ListMatches(Session, MinPlayers, MaxPlayers, Limit, Label, Query, Authoritative, ListMatchesSuccessCallback, ListMatchesErrorCallback); - }; - - auto RPCErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Matches RPC Error: %s"), *Error.Message); - TestFalse("List Matches Test Failed: RPC Failed", true); - StopTest(); - }; - - Client->RPC(Session, TEXT("create_matches"), {}, RPCSuccessCallback, RPCErrorCallback); - }; - - // Auth Error - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("List Matches Error: %s"), *Error.Message); - TestFalse("List Matches Test Failed: Auth Error", true); - StopTest(); - }; - - // Call the AuthenticateEmail function - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, errorCallback); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -}*/ \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_RestoreSession.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_RestoreSession.cpp deleted file mode 100644 index 12ff7b727..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_RestoreSession.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTestBase.h" - -// Restore Session check with example JWT tokens -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RestoreSession, FNakamaTestBase, "Nakama.Base.Sessions.RestoreSession", NAKAMA_MODULE_TEST_MASK) -inline bool RestoreSession::RunTest(const FString& Parameters) -{ - // First Token - const FString FirstToken = TEXT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwifQ.gzLaMQPaj5wEKoskOSALIeJLOYXEVFoPx3KY0Jm1EVU"); - UNakamaSession* FirstSession = UNakamaSession::RestoreSession(FirstToken, TEXT("")); - bool bDidFirstTokenFail = false; - - if(FirstSession->GetAuthToken() != FirstToken) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: AuthToken")); - TestFalse("Restore Session Test Failed: AuthToken", true); - bDidFirstTokenFail = true; - } - if(FirstSession->GetVariables().Num() != 0) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: Variables")); - TestFalse("Restore Session Test Failed: Variables", true); - bDidFirstTokenFail = true; - } - - if(bDidFirstTokenFail) - { - StopTest(); - } - - // Second Token (with variables and more checks) - const FString SecondToken = TEXT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwiLCJ2cnMiOnsiazEiOiJ2MSIsImsyIjoidjIifX0.Hs9ltsNmtrTJXi2U21jjuXcd-3DMsyv4W6u1vyDBMTo"); - UNakamaSession* SecondSession = UNakamaSession::RestoreSession(SecondToken, TEXT("")); - bool bDidSecondTokenFail = false; - - if(SecondSession->GetAuthToken() != SecondToken) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: AuthToken")); - TestFalse("Restore Session Test Failed: AuthToken", true); - bDidSecondTokenFail = true; - } - if(SecondSession->GetUsername() != TEXT("vTGdGHyxvl")) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: Username")); - TestFalse("Restore Session Test Failed: Username", true); - bDidSecondTokenFail = true; - } - if(SecondSession->GetUserId() != TEXT("f4158f2b-80f3-4926-946b-a8ccfc165490")) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: UserId")); - TestFalse("Restore Session Test Failed: UserId", true); - bDidSecondTokenFail = true; - } - if(SecondSession->GetVariable(TEXT("k1")) != TEXT("v1")) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: Variable k1")); - TestFalse("Restore Session Test Failed: Variable k1", true); - bDidSecondTokenFail = true; - } - if(SecondSession->GetVariable(TEXT("k2")) != TEXT("v2")) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Failed: Variable k2")); - TestFalse("Restore Session Test Failed: Variable k2", true); - bDidSecondTokenFail = true; - } - - if(bDidSecondTokenFail) - { - StopTest(); - } - - if(!bDidFirstTokenFail && !bDidSecondTokenFail) - { - UE_LOG(LogTemp, Warning, TEXT("Restore Session Test Passed: Both Tokens")); - TestTrue("Restore Session Test Passed: Both Tokens", true); - StopTest(); - } - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Error Unauthenticated -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(RestoreSession2, FNakamaTestBase, "Nakama.Base.Sessions.RestoreSession2", NAKAMA_MODULE_TEST_MASK) -inline bool RestoreSession2::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - const int ExpirePeriodMinutes = 121; - const int RefreshExpirePeriodMinutes = 7201; - - UNakamaSession* RestoredSession = UNakamaSession::RestoreSession(Session->GetAuthToken(), Session->GetRefreshToken()); - UE_LOG (LogTemp, Display, TEXT("Session Token: %s"), *Session->GetAuthToken()); - UE_LOG (LogTemp, Display, TEXT("Session IsExpired: %hhd"), Session->IsExpired()); - - // Get the current timestamp in milliseconds - //int64 CurrentTimestampMs = FPlatformTime::Seconds() * 1000; - - // Calculate the expiration time in milliseconds - //FDateTime ExpireTimeMs = RestoredSession->GetExpireTime(); // Assuming getExpireTime() returns the expiration time in milliseconds - - if(Session->IsRefreshExpired()) - { - UE_LOG(LogTemp, Warning, TEXT("Original session refresh token must not be expired")); - TestFalse("RestoreSession Failed: Session Refresh Token Expired", true); - StopTest(); - } - else if(Session->IsExpired()) - { - UE_LOG(LogTemp, Warning, TEXT("Original session token must not be expired")); - TestFalse("RestoreSession Failed: Session Auth Token Expired", true); - StopTest(); - } - else if(!RestoredSession->IsExpiredTime(FDateTime::UtcNow() + FTimespan::FromMinutes(ExpirePeriodMinutes))) - { - UE_LOG(LogTemp, Warning, TEXT("Restored session token must be expired after: %s minutes"), *FString::FromInt(ExpirePeriodMinutes)); - TestFalse("RestoreSession Failed: Auth Token", true); - StopTest(); - } - else if(!RestoredSession->IsRefreshExpiredTime(FDateTime::UtcNow() + FTimespan::FromMinutes(RefreshExpirePeriodMinutes))) - { - UE_LOG(LogTemp, Warning, TEXT("Restored session token must be expired after: %s minutes"), *FString::FromInt(RefreshExpirePeriodMinutes)); - TestFalse("RestoreSession Failed: Refresh Token", true); - StopTest(); - } - else - { - // Test Account Request - auto GetAccountSuccessCallback = [&](const FNakamaAccount& Account) - { - UE_LOG(LogTemp, Warning, TEXT("Account ID: %s"), *Account.User.Id); - UE_LOG(LogTemp, Warning, TEXT("Account Retreived: %ls"), *Account.User.CreatedAt.ToString()); - - // Successful Test: - TestTrue("RestoreSession Test Passed", !Account.User.Id.IsEmpty()); - StopTest(); - }; - - auto GetAccountErrorCallback = [&](const FNakamaError& Error) - { - UE_LOG(LogTemp, Error, TEXT("Account retrieval error. ErrorMessage: %s"), *Error.Message); - TestFalse("RestoreSession Test Failed: Final Account Step", true); - StopTest(); - }; - - Client->GetAccount(RestoredSession, GetAccountSuccessCallback, GetAccountErrorCallback); - } - }; - - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, {}); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Private/Tests/Test_Storage.cpp b/Nakama/Source/NakamaTests/Private/Tests/Test_Storage.cpp deleted file mode 100644 index 8d96ad127..000000000 --- a/Nakama/Source/NakamaTests/Private/Tests/Test_Storage.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Tests/Test_Storage.h" - -#include "NakamaTestBase.h" - -// Write Storage Invalid Argument -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(WriteStorageInvalidArgument, FNakamaTestBase, "Nakama.Base.Storage.InvalidArgument", NAKAMA_MODULE_TEST_MASK) -inline bool WriteStorageInvalidArgument::RunTest(const FString& Parameters) -{ - // TODO: This test ends up being quite slow - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - auto errorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("WriteStorageInvalidArgument. ErrorMessage: %s"), *Error.Message); - UE_LOG(LogTemp, Display, TEXT("WriteStorageInvalidArgument. ErrorCode: %d"), Error.Code); - TestTrue("Write Storage Invalid Argument Passed.", Error.Code == ENakamaErrorCode::InvalidArgument); - StopTest(); - }; - - // Object to write - TArray Objects; - - FNakamaStoreObjectWrite Object1; - Object1.Collection = TEXT("candies"); - Object1.Key = TEXT("test"); - Object1.Value = TEXT("25"); // Invalid Json! - Objects.Add(Object1); - - // Only want to check error? - Client->WriteStorageObjects(Session, Objects, {}, errorCallback); - }; - - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, {}); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Write Storage Object then verify read -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(WriteStorage, FNakamaTestBase, "Nakama.Base.Storage.Write", NAKAMA_MODULE_TEST_MASK) -inline bool WriteStorage::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - auto WriteErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("Write Storage Object Failed. ErrorMessage: %s"), *Error.Message); - TestFalse("Write Storage Object Failed.", true); - StopTest(); - }; - - auto WriteSuccessCallback = [this](const FNakamaStoreObjectAcks& Acks) - { - if(Acks.StorageObjects.Num() == 1) - { - UE_LOG(LogTemp, Display, TEXT("Write Ok. Version: %s"), *Acks.StorageObjects[0].Version); - - auto ListErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("List Storage Object Failed. ErrorMessage: %s"), *Error.Message); - TestFalse("List Storage Object Failed.", true); - StopTest(); - }; - - auto ListSuccessCallback = [this](const FNakamaStorageObjectList& ObjectList) - { - UE_LOG(LogTemp, Display, TEXT("List Storage Object Success. Objects: %d"), ObjectList.Objects.Num()); - TestTrue("List Storage Object Success.", ObjectList.Objects.Num() > 0); - StopTest(); - }; - - Client->ListUsersStorageObjects(Session, "candies", Session->GetUserId(), {}, {}, ListSuccessCallback, ListErrorCallback); - } - else - { - UE_LOG(LogTemp, Display, TEXT("Write Storage Object Failed.")); - TestFalse("Write Storage Object Failed.", true); - StopTest(); - } - }; - - // Object to write - TArray Objects; - - FNakamaStoreObjectWrite Object1; - Object1.Collection = TEXT("candies"); - Object1.Key = TEXT("Ice cream"); - Object1.Value = TEXT("{ \"price\": 25 }"); - Object1.PermissionRead = ENakamaStoragePermissionRead::OWNER_READ; - Object1.PermissionWrite = ENakamaStoragePermissionWrite::OWNER_WRITE; - Objects.Add(Object1); - - // Only want to check error? - Client->WriteStorageObjects(Session, Objects, WriteSuccessCallback, WriteErrorCallback); - }; - - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, {}); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} - -// Write Storage Object then verify cursor -IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(WriteStorageCursor, FNakamaStorageTestBase, "Nakama.Base.Storage.WriteCursor", NAKAMA_MODULE_TEST_MASK) -inline bool WriteStorageCursor::RunTest(const FString& Parameters) -{ - // Initiates the test - InitiateTest(); - - // Define success callback - auto successCallback = [this](UNakamaSession* session) - { - // Set the session for later use - Session = session; - - auto WriteErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("Write Storage Object Failed. ErrorMessage: %s"), *Error.Message); - TestFalse("Write Storage Object Failed.", true); - StopTest(); - }; - - auto WriteSuccessCallback = [this](const FNakamaStoreObjectAcks& Acks) - { - if(Acks.StorageObjects.Num() == NumCandies) - { - UE_LOG(LogTemp, Display, TEXT("Write Ok. Version: %s"), *Acks.StorageObjects[0].Version); - - auto ListErrorCallback = [this](const FNakamaError& Error) - { - UE_LOG(LogTemp, Display, TEXT("List Storage Object Failed. ErrorMessage: %s"), *Error.Message); - TestFalse("List Storage Object Failed.", true); - StopTest(); - }; - - auto ListFirstSuccessCallback = [this](const FNakamaStorageObjectList& FirstObjectList) - { - UE_LOG(LogTemp, Display, TEXT("Cursor: %d"), FirstObjectList.Objects.Num()); - - // Second: - auto ListSecondSuccessCallback = [this](const FNakamaStorageObjectList& SecondObjectList) - { - UE_LOG(LogTemp, Display, TEXT("List Second Storage Object Success. Objects: %d"), SecondObjectList.Objects.Num()); - TestTrue("Storage Object Cursor Passed.", SecondObjectList.Objects.Num() > 0); - StopTest(); - }; - - auto ListSecondErrorCallback = [this](const FNakamaError& Error) - { - // This typically means the cursor is invalid - UE_LOG(LogTemp, Display, TEXT("List Storage Object Second Failed. ErrorMessage: %s"), *Error.Message); - TestFalse("List Storage Object Second Failed.", true); - StopTest(); - }; - - Client->ListUsersStorageObjects(Session, "candies", Session->GetUserId(), 10, FirstObjectList.Cursor, ListSecondSuccessCallback, ListSecondErrorCallback); - }; - - Client->ListUsersStorageObjects(Session, "candies", Session->GetUserId(), {}, {}, ListFirstSuccessCallback, ListErrorCallback); - } - else - { - UE_LOG(LogTemp, Display, TEXT("Write Storage Object Failed.")); - TestFalse("Write Storage Object Failed.", true); - StopTest(); - } - }; - - // Object to write - TArray Objects; - - for (int i = 0; i < NumCandies; i++) - { - FNakamaStoreObjectWrite Object; - Object.Collection = TEXT("candies"); - Object.Key = FString::Printf(TEXT("Ice cream%d"), i); - Object.Value = TEXT("{ \"price\": 25 }"); - Object.PermissionRead = ENakamaStoragePermissionRead::OWNER_READ; - Object.PermissionWrite = ENakamaStoragePermissionWrite::OWNER_WRITE; - Objects.Add(Object); - } - - // Only want to check error? - Client->WriteStorageObjects(Session, Objects, WriteSuccessCallback, WriteErrorCallback); - }; - - Client->AuthenticateDevice("mytestdevice0000", true, {}, {}, successCallback, {}); - - // Wait for authentication to complete - ADD_LATENT_AUTOMATION_COMMAND(FWaitForAsyncQueries(this)); - - // Return true to indicate the test is complete - return true; -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/NakamaTestBase.h b/Nakama/Source/NakamaTests/Public/NakamaTestBase.h deleted file mode 100644 index d0974318b..000000000 --- a/Nakama/Source/NakamaTests/Public/NakamaTestBase.h +++ /dev/null @@ -1,150 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaClient.h" -#include "NakamaLogger.h" -#include "Misc/AutomationTest.h" -#include "Misc/CommandLine.h" -#include "Misc/EngineVersionComparison.h" - -#if UE_VERSION_OLDER_THAN(5, 5, 0) -#define NAKAMA_MODULE_TEST_MASK EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter -#else -#define NAKAMA_MODULE_TEST_MASK EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::ProductFilter -#endif - - -// The base class for the tests -class FNakamaTestBase : public FAutomationTestBase -{ -public: - FNakamaTestBase (const FString& InName, const bool bInComplexTask) : FAutomationTestBase(InName, bInComplexTask), Client(nullptr), Socket(nullptr), Session(nullptr) - { - bHasFinished = false; - } - - virtual bool SuppressLogWarnings() override { return true; } // Toggle this to see warnings! - void StopTest() { bHasFinished = true; } - bool IsFinished() const { return bHasFinished; } - void InitiateTest() - { - if(FCommandLine::IsInitialized()) - { - FString GetHostName; - if (FParse::Value(FCommandLine::Get(), TEXT("hostname="), GetHostName)) - { - Hostname = GetHostName; - } - - FString GetServerKey; - if (FParse::Value(FCommandLine::Get(), TEXT("serverkey="), GetServerKey)) - { - ServerKey = GetServerKey; - } - - int32 GetPort; - if (FParse::Value(FCommandLine::Get(), TEXT("port="), GetPort)) - { - Port = GetPort; - } - - bool GetUseSSL; - if (FParse::Bool(FCommandLine::Get(), TEXT("useSSL"), GetUseSSL)) - { - UseSSL = true; - } - - FString GetServerHttpKey; - if (FParse::Value(FCommandLine::Get(), TEXT("serverhttpkey="), GetServerHttpKey)) - { - ServerHttpKey = GetServerHttpKey; - } - - double GetTimeout; - if (FParse::Value(FCommandLine::Get(), TEXT("timeout="), GetTimeout)) - { - Timeout = GetTimeout; - } - } - else - { - UE_LOG(LogTemp, Warning, TEXT("Command Line is not initialized!")); - } - - bHasFinished = false; - Client = CreateClient(); - - UNakamaLogger::EnableLogging(true); - UNakamaLogger::SetLogLevel(ENakamaLogLevel::Debug); - } - double StartTestTime = 0; - - // Set Timeout accordingly - double Timeout = 60.0; - - UPROPERTY() - UNakamaClient *Client; - - UPROPERTY() - UNakamaRealtimeClient *Socket; - - UPROPERTY() - UNakamaSession *Session; - - UNakamaClient* CreateClient() const - { - return UNakamaClient::CreateDefaultClient(ServerKey, Hostname, Port, UseSSL, true); - } - - // Parameters - FString ServerKey = TEXT("defaultkey"); - FString Hostname = TEXT("127.0.0.1"); - int32 Port = 7350; - bool UseSSL = false; - FString ServerHttpKey = TEXT("defaulthttpkey"); - -private: - bool bHasFinished = false; -}; - -// Helper class to wait for async queries to complete (will wait forever) -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForAsyncQueries, FNakamaTestBase*, Base); -inline bool FWaitForAsyncQueries::Update() -{ - // Check if the timer has been initialized - if (Base->StartTestTime == 0) - { - // Get the current time - Base->StartTestTime = FPlatformTime::Seconds(); - } - - // Get the current time - double CurrentTime = FPlatformTime::Seconds(); - - // Calculate elapsed time since the timer was started - double ElapsedTime = CurrentTime - Base->StartTestTime; - - // If X seconds have passed, end the test - if (ElapsedTime >= Base->Timeout) - { - Base->StopTest(); - } - - return Base->IsFinished(); -} \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/NakamaTests.h b/Nakama/Source/NakamaTests/Public/NakamaTests.h deleted file mode 100644 index 630ede68a..000000000 --- a/Nakama/Source/NakamaTests/Public/NakamaTests.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogNakamaTests, Log, All); - - -class FNakamaTestsModule : public IModuleInterface -{ -public: - - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_AuthoritativeMatch.h b/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_AuthoritativeMatch.h deleted file mode 100644 index 478600151..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_AuthoritativeMatch.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaAuthoritativeMatchTestBase : public FNakamaTestBase -{ -public: - FNakamaAuthoritativeMatchTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask), Client2(nullptr), Socket2(nullptr), Session2(nullptr) - { - - } - - virtual bool SuppressLogWarnings() override { return true; } - - UNakamaClient* Client2; - UNakamaRealtimeClient* Socket2; - UNakamaSession* Session2; - - FString GetMatchIdFromJsonString(const FString& JsonString) const; - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_FollowUsers.h b/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_FollowUsers.h deleted file mode 100644 index dbc730b19..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_FollowUsers.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -class FNakamaFollowUsersTestBase : public FNakamaTestBase -{ -public: - FNakamaFollowUsersTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask), Client2(nullptr), Socket2(nullptr), Session2(nullptr) - { - - } - - virtual bool SuppressLogWarnings() override { return true; } - - UNakamaClient* Client2; - UNakamaRealtimeClient* Socket2; - UNakamaSession* Session2; -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Match.h b/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Match.h deleted file mode 100644 index 2419dd68c..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Match.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaMatchTestBase : public FNakamaTestBase -{ -public: - FNakamaMatchTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask), Client2(nullptr), Socket2(nullptr) - { - - } - virtual bool SuppressLogWarnings() override { return true; } - - UNakamaClient* Client2; - UNakamaRealtimeClient* Socket2; - UNakamaSession* Session2; - - TOptional MinCount = 2; - TOptional MaxCount = 2; - const FString Query = "*"; - const TMap StringProperties = {}; - const TMap NumericProperties = {}; - const int CountMultiple = 1; -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Parties.h b/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Parties.h deleted file mode 100644 index d7d00a84e..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Parties.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaPartiesTestBase : public FNakamaTestBase -{ -public: - FNakamaPartiesTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask), Client2(nullptr), Socket2(nullptr), Session2(nullptr) - { - - } - - virtual bool SuppressLogWarnings() override { return true; } - - UNakamaClient* Client2; - UNakamaRealtimeClient* Socket2; - UNakamaSession* Session2; - - FNakamaParty Party; - - void SetupClient2AndJoinParty(); - void SetupClient2AndRequestJoinParty(); - void SetupClient2AndReceiveRequestJoinParty(); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Tournament.h b/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Tournament.h deleted file mode 100644 index 190c73a30..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Realtime/Test_Tournament.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaTournamentTestBase : public FNakamaTestBase -{ -public: - FNakamaTournamentTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask), Client2(nullptr), Socket2(nullptr) - { - - } - virtual bool SuppressLogWarnings() override { return true; } - - UNakamaClient* Client2; - UNakamaRealtimeClient* Socket2; - UNakamaSession* Session2; - - static uint64 GetCurrentUnixTimestampInSeconds() - { - return FDateTime::Now().ToUnixTimestamp(); - } - - int64 StartTime = GetCurrentUnixTimestampInSeconds(); // starts now in seconds - int32 Duration = 5; // in seconds - FString Operator = "best"; // one of : "best", "set", "incr" - FString ResetSchedule = ""; // none - int64 EndTime = StartTime + 5; // end after 5 sec - int32 MaxSize = 10000; // first 10,000 players who join - int32 MaxNumScore = 3; // each player can have 3 attempts to score - bool JoinRequired = true; // must join to compete - - FString GetTournamentIdFromJsonString(const FString& JsonString) const; - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Test_Friends.h b/Nakama/Source/NakamaTests/Public/Tests/Test_Friends.h deleted file mode 100644 index 6eb52787e..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Test_Friends.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaFriendsTestBase : public FNakamaTestBase -{ -public: - FNakamaFriendsTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask) { } - virtual bool SuppressLogWarnings() override { return true; } - - // Other - UPROPERTY() - TArray FriendIds; - - int FriendsToAdd = 5; - int ListFriendsLimit = 1; - -}; - diff --git a/Nakama/Source/NakamaTests/Public/Tests/Test_Groups.h b/Nakama/Source/NakamaTests/Public/Tests/Test_Groups.h deleted file mode 100644 index 4159aa866..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Test_Groups.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the group tests -// Some tests were moved into one -class FNakamaGroupsTestBase : public FNakamaTestBase -{ -public: - FNakamaGroupsTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask) { } - virtual bool SuppressLogWarnings() override { return true; } - - FString GroupName = TEXT("We're-Nakama-Lovers"); - //FString GroupName = TEXT("Test7"); - - void DoListGroups(); - void CreateGroup(); - void UpdateGroup(const FString& GroupId); - void ListGroupUsers(const FString& GroupId); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaTests/Public/Tests/Test_Storage.h b/Nakama/Source/NakamaTests/Public/Tests/Test_Storage.h deleted file mode 100644 index fdb50d36b..000000000 --- a/Nakama/Source/NakamaTests/Public/Tests/Test_Storage.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaTestBase.h" - -// The base class for the tests -class FNakamaStorageTestBase : public FNakamaTestBase -{ -public: - FNakamaStorageTestBase(const FString& InName, bool bInComplexTask) : FNakamaTestBase(InName, bInComplexTask) { } - virtual bool SuppressLogWarnings() override { return true; } - - - int NumCandies = 25; - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaAccount.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaAccount.cpp deleted file mode 100644 index 81267ff20..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaAccount.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaAccount.h" -#include "NakamaUtils.h" - -FNakamaAccount::FNakamaAccount(): VerifyTime(FDateTime::MinValue()), DisableTime(FDateTime::MinValue()) -{ -} - -FNakamaAccount::FNakamaAccount(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid()) - { - // Failed to deserialize JSON or JSON object is invalid - return; - } - - - const TSharedPtr* UserJsonObject; - if (JsonObject->TryGetObjectField(TEXT("user"), UserJsonObject)) - { - User = FNakamaUser(*UserJsonObject); - } - - JsonObject->TryGetStringField(TEXT("wallet"), Wallet); - JsonObject->TryGetStringField(TEXT("email"), Email); - JsonObject->TryGetStringField(TEXT("custom_id"), CustomId); - - if (JsonObject->HasTypedField(TEXT("devices"))) - { - const TArray>* DevicesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("devices"), DevicesJsonArray)) - { - for (const TSharedPtr& DeviceJson : *DevicesJsonArray) - { - if (TSharedPtr DeviceJsonObject = DeviceJson->AsObject()) - { - FNakamaAccountDevice Device(DeviceJsonObject); - Devices.Add(Device); - } - } - } - } - - FString VerifyTimeString; - if (JsonObject->TryGetStringField(TEXT("verify_time"), VerifyTimeString)) - { - FDateTime::ParseIso8601(*VerifyTimeString, VerifyTime); - } - else - { - VerifyTime = FDateTime(); // Set to default value when the field is not found - } - - FString DisableTimeString; - if (JsonObject->TryGetStringField(TEXT("disable_time"), DisableTimeString)) - { - FDateTime::ParseIso8601(*DisableTimeString, DisableTime); - } - else - { - DisableTime = FDateTime(); // Set to default value when the field is not found - } - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaAccountDevice.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaAccountDevice.cpp deleted file mode 100644 index d868c65c8..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaAccountDevice.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaAccountDevice.h" - -#include "NakamaUtils.h" - -FNakamaAccountDevice::FNakamaAccountDevice(const FString& JsonString) : FNakamaAccountDevice(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaAccountDevice::FNakamaAccountDevice(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - if (JsonObject->HasField(TEXT("id"))) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - } - - const TSharedPtr* VarsJsonObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("vars"), VarsJsonObject)) - { - Vars.Empty(); - - for (const auto& Entry : (*VarsJsonObject)->Values) - { - FString Key = Entry.Key; - FString Value = Entry.Value->AsString(); - - Vars.Add(Key, Value); - } - } - } -} - -FNakamaAccountDevice::FNakamaAccountDevice() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaChannelTypes.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaChannelTypes.cpp deleted file mode 100644 index 20066f66d..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaChannelTypes.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaChannelTypes.h" -#include "NakamaUtils.h" - -FNakamaChannelMessage::FNakamaChannelMessage(const FString& JsonString) : FNakamaChannelMessage(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaChannelMessage::FNakamaChannelMessage(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("channel_id"), ChannelId); - JsonObject->TryGetStringField(TEXT("message_id"), MessageId); - JsonObject->TryGetNumberField(TEXT("code"), code); - JsonObject->TryGetStringField(TEXT("sender_id"), SenderId); - JsonObject->TryGetStringField(TEXT("username"), Username); - JsonObject->TryGetStringField(TEXT("content"), Content); - - FString CreateTimeString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreateTimeString)) { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - - FString UpdateTimeString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdateTimeString)) { - FDateTime::ParseIso8601(*UpdateTimeString, UpdateTime); - } - - JsonObject->TryGetBoolField(TEXT("persistent"), Persistent); - JsonObject->TryGetStringField(TEXT("room_name"), RoomName); - JsonObject->TryGetStringField(TEXT("group_id"), GroupId); - JsonObject->TryGetStringField(TEXT("user_id_one"), UserIdOne); - JsonObject->TryGetStringField(TEXT("user_id_two"), UserIdTwo); - } -} - -FNakamaChannelMessage::FNakamaChannelMessage() -{ - -} - -FNakamaChannelMessageAck::FNakamaChannelMessageAck(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TSharedPtr* ChannelMessageObject; - if (JsonObject->TryGetObjectField(TEXT("channel_message_ack"), ChannelMessageObject)) { - - (*ChannelMessageObject)->TryGetStringField(TEXT("channel_id"), ChannelId); - (*ChannelMessageObject)->TryGetStringField(TEXT("message_id"), MessageId); - (*ChannelMessageObject)->TryGetStringField(TEXT("username"), Username); - (*ChannelMessageObject)->TryGetNumberField(TEXT("code"), code); - - FString CreateTimeString; - if ((*ChannelMessageObject)->TryGetStringField(TEXT("create_time"), CreateTimeString)) { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - - FString UpdateTimeString; - if ((*ChannelMessageObject)->TryGetStringField(TEXT("update_time"), UpdateTimeString)) { - FDateTime::ParseIso8601(*UpdateTimeString, UpdateTime); - } - - (*ChannelMessageObject)->TryGetBoolField(TEXT("persistent"), Persistent); - (*ChannelMessageObject)->TryGetStringField(TEXT("room_name"), RoomName); - } - } - -} - -FNakamaChannelMessageAck::FNakamaChannelMessageAck() -{ -} - -FNakamaChannelMessageList::FNakamaChannelMessageList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* MessagesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("messages"), MessagesJsonArray)) - { - for (const TSharedPtr& MessageJsonValue : *MessagesJsonArray) - { - if (TSharedPtr MessageJsonObject = MessageJsonValue->AsObject()) - { - FNakamaChannelMessage Message(MessageJsonObject); - Messages.Add(Message); - } - } - } - - JsonObject->TryGetStringField(TEXT("next_cursor"), NextCursor); - JsonObject->TryGetStringField(TEXT("prev_cursor"), PrevCursor); - } -} - - -FNakamaChannelMessageList::FNakamaChannelMessageList() -{ -} - - -FNakamaChannelPresenceEvent::FNakamaChannelPresenceEvent(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("channel_id"), ChannelId); - - const TArray>* JoinsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("joins"), JoinsJsonArray)) - { - for (const TSharedPtr& JoinJsonValue : *JoinsJsonArray) - { - if (TSharedPtr JoinJsonObject = JoinJsonValue->AsObject()) - { - FNakamaUserPresence JoinPresence(JoinJsonObject); - Joins.Add(JoinPresence); - } - } - } - - const TArray>* LeavesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("leaves"), LeavesJsonArray)) - { - for (const TSharedPtr& LeaveJsonValue : *LeavesJsonArray) - { - if (TSharedPtr LeaveJsonObject = LeaveJsonValue->AsObject()) - { - FNakamaUserPresence LeavePresence(LeaveJsonObject); - Leaves.Add(LeavePresence); - } - } - } - - JsonObject->TryGetStringField(TEXT("room_name"), RoomName); - JsonObject->TryGetStringField(TEXT("group_id"), GroupId); - JsonObject->TryGetStringField(TEXT("user_id_one"), UserIdOne); - JsonObject->TryGetStringField(TEXT("user_id_two"), UserIdTwo); - } -} - -FNakamaChannelPresenceEvent::FNakamaChannelPresenceEvent() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaChat.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaChat.cpp deleted file mode 100644 index 91918de0a..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaChat.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaChat.h" -#include "Dom/JsonObject.h" -#include "Serialization/JsonSerializer.h" - -FNakamaChannel::FNakamaChannel() -{ - -} - -FNakamaChannel::FNakamaChannel(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TSharedPtr* ChannelObjectPtr; - if (JsonObject->TryGetObjectField(TEXT("channel"), ChannelObjectPtr)) - { - TSharedPtr ChannelObject = *ChannelObjectPtr; - - ChannelObject->TryGetStringField(TEXT("id"), Id); - ChannelObject->TryGetStringField(TEXT("room_name"), RoomName); - ChannelObject->TryGetStringField(TEXT("group_id"), GroupId); - ChannelObject->TryGetStringField(TEXT("user_id_one"), UserIdOne); - ChannelObject->TryGetStringField(TEXT("user_id_two"), UserIdTwo); - - const TArray>* PresencesJsonArray; - if (ChannelObject->TryGetArrayField(TEXT("presences"), PresencesJsonArray)) - { - for (const TSharedPtr& PresenceJsonValue : *PresencesJsonArray) - { - if (TSharedPtr PresenceJsonObject = PresenceJsonValue->AsObject()) - { - FNakamaUserPresence Presence(PresenceJsonObject); - Presences.Add(Presence); - } - } - } - - const TSharedPtr* SelfObjectPtr; - if (ChannelObject->TryGetObjectField(TEXT("self"), SelfObjectPtr)) - { - Me = FNakamaUserPresence(*SelfObjectPtr); - } - } - } -} \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaClient.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaClient.cpp deleted file mode 100644 index fe831f84a..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaClient.cpp +++ /dev/null @@ -1,9298 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaClient.h" -#include "NakamaUtils.h" -#include "NakamaRealtimeClient.h" -#include "NakamaSession.h" -#include "NakamaLogger.h" -#include "GenericPlatform/GenericPlatformHttp.h" -#include "Interfaces/IHttpResponse.h" -#include "Misc/Optional.h" - -void UNakamaClient::InitializeClient(const FString& InHostname, int32 InPort, const FString& InServerKey, - bool bInUseSSL) -{ - Hostname = InHostname; - Port = InPort; - ServerKey = InServerKey; - bUseSSL = bInUseSSL; -} - -void UNakamaClient::InitializeSystem(const FString& InServerKey, const FString& Host, int32 InPort, bool UseSSL, - bool EnableDebug) -{ - if (EnableDebug) - { - bEnableDebug = true; - } - - InitializeClient(Host, InPort, InServerKey, UseSSL); - bIsActive = true; -} - -void UNakamaClient::Disconnect() -{ - if(IsValidLowLevel()) - { - CancelAllRequests(); - } -} - -void UNakamaClient::Destroy() -{ - bIsActive = false; - ConditionalBeginDestroy(); -} - -void UNakamaClient::SetTimeout(float InTimeout) -{ - Timeout = InTimeout; -} - -float UNakamaClient::GetTimeout() -{ - return Timeout; -} - -void UNakamaClient::BeginDestroy() -{ - UObject::BeginDestroy(); - bIsActive = false; -} - -UNakamaClient* UNakamaClient::CreateDefaultClient( - const FString& ServerKey, - const FString& Host, - int32 Port, - bool UseSSL, - bool EnableDebug) -{ - UNakamaClient* NewClient = NewObject((UObject*)GetTransientPackage(), UNakamaClient::StaticClass()); - NewClient->InitializeSystem(ServerKey, Host, Port, UseSSL, EnableDebug); - - if (EnableDebug) - { - UNakamaLogger::EnableLogging(true); - UNakamaLogger::SetLogLevel(ENakamaLogLevel::Debug); - } - - return NewClient; -} - -/** - * Authentication - */ - -void UNakamaClient::AuthenticateCustom(const FString& UserID, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - // A custom identifier must contain alphanumeric - // characters with dashesand be between 6 and 128 bytes. - - AuthenticateCustom(UserID, Username, CreateAccount, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateEmail( - const FString& Email, - const FString& Password, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - // An email address must be valid as defined by RFC-5322 and passwords must be at least 8 characters. - - AuthenticateEmail(Email, Password, Username, CreateAccount, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateDevice( - const FString& DeviceID, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - const FOnAuthUpdate& Success, - const FOnError& Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - // A device identifier must contain alphanumeric characters with dashes and be between 10 and 128 bytes. - - const auto OptUsername = FNakamaUtils::CreateOptional(Username, FString()); - - AuthenticateDevice(DeviceID, CreateAccount, OptUsername, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateSteam( - const FString& SteamToken, - const FString& Username, - bool CreateAccount, - bool ImportFriends, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateSteam(SteamToken, Username, CreateAccount, ImportFriends, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateGoogle( - const FString& AccessToken, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateGoogle(AccessToken, Username, CreateAccount, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateGameCenter( - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateGameCenter( - PlayerId, - BundleId, - TimeStampSeconds, - Salt, - Signature, - PublicKeyUrl, - Username, - CreateAccount, - Vars, - successCallback, - errorCallback - ); -} - -void UNakamaClient::AuthenticateFacebook( - const FString& AccessToken, - const FString& Username, - bool CreateAccount, - bool ImportFriends, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateFacebook(AccessToken, Username, CreateAccount, ImportFriends, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateApple( - const FString& Token, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateApple(Token, Username, CreateAccount, Vars, successCallback, errorCallback); -} - -void UNakamaClient::AuthenticateRefresh( - UNakamaSession* Session, - FOnAuthUpdate Success, - FOnError Error) -{ - auto successCallback = [this, Success](UNakamaSession* session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateRefresh(Session, successCallback, errorCallback); -} - -/** - * Sessions - */ - -void UNakamaClient::RestoreSession( - const FString& Token, - const FString& RefreshToken, - UNakamaSession*& RestoredSession) -{ - UNakamaSession* Session = UNakamaSession::RestoreSession(Token, RefreshToken); - RestoredSession = Session; -} - -/** - * Linking Accounts - */ - -void UNakamaClient::LinkCustom( - UNakamaSession *Session, - const FString& CustomId, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkCustom(Session, CustomId, successCallback, errorCallback); -} - -void UNakamaClient::LinkDevice( - UNakamaSession *Session, - const FString& DeviceId, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkDevice(Session, DeviceId, successCallback, errorCallback); -} - -void UNakamaClient::LinkEmail( - UNakamaSession *Session, - const FString& Email, - const FString& Password, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkEmail(Session, Email, Password, successCallback, errorCallback); -} - -void UNakamaClient::LinkFacebook( - UNakamaSession *Session, - const FString& AccessToken, - bool ImportFriends, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkFacebook(Session, AccessToken, ImportFriends, successCallback, errorCallback); -} - -void UNakamaClient::LinkGameCenter( - UNakamaSession *Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkGameCenter( - Session, - PlayerId, - BundleId, - TimeStampSeconds, - Salt, - Signature, - PublicKeyUrl, - successCallback, - errorCallback - ); -} - -void UNakamaClient::LinkGoogle( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkGoogle(Session, AccessToken, successCallback, errorCallback); -} - -void UNakamaClient::LinkSteam( - UNakamaSession *Session, - const FString& SteamToken, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkSteam(Session, SteamToken, successCallback, errorCallback); -} - -void UNakamaClient::LinkApple( - UNakamaSession *Session, - const FString& Token, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LinkApple(Session, Token, successCallback, errorCallback); -} - -/** - * Unlinking Account - */ - -void UNakamaClient::UnLinkCustom( - UNakamaSession *Session, - const FString& CustomId, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkCustom(Session, CustomId, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkDevice( - UNakamaSession *Session, - const FString& DeviceId, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkDevice(Session, DeviceId, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkEmail( - UNakamaSession *Session, - const FString& Email, - const FString& Password, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkEmail(Session, Email, Password, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkFacebook( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkFacebook(Session, AccessToken, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkGameCenter( - UNakamaSession *Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkGameCenter( - Session, - PlayerId, - BundleId, - TimeStampSeconds, - Salt, - Signature, - PublicKeyUrl, - successCallback, - errorCallback - ); -} - -void UNakamaClient::UnLinkGoogle( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkGoogle(Session, AccessToken, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkSteam( - UNakamaSession *Session, - const FString& SteamToken, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkSteam(Session, SteamToken, successCallback, errorCallback); -} - -void UNakamaClient::UnLinkApple( - UNakamaSession *Session, - const FString& Token, - FOnLinkSuccess Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnLinkApple(Session, Token, successCallback, errorCallback); -} - -/** - * Refresh Session - */ - -void UNakamaClient::RefreshSession( - UNakamaSession *Session, - FOnAuthRefresh Success, - FOnAuthRefreshError Error) -{ - auto successCallback = [this, Success](UNakamaSession* Session) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(Session); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateRefresh(Session, successCallback, errorCallback); -} - -/** - * Import Facebook Friends - */ - -void UNakamaClient::ImportFacebookFriends( - UNakamaSession* Session, - const FString& Token, - bool Reset, - FOnImportFacebookFriends Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ImportFacebookFriends(Session, Token, Reset, successCallback, errorCallback); -} - -/** - * Import Steam Friends - */ - -void UNakamaClient::ImportSteamFriends( - UNakamaSession* Session, - const FString& SteamToken, - bool Reset, - FOnImportSteamFriends Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ImportSteamFriends(Session, SteamToken, Reset, successCallback, errorCallback); -} - -/** - * Get Account Info - */ - -void UNakamaClient::GetUserAccount( - UNakamaSession *Session, - FOnUserAccountInfo Success, - FOnError Error) -{ - GetAccount(Session, Success, Error); -} - -void UNakamaClient::GetAccount( - UNakamaSession *Session, - FOnUserAccountInfo Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaAccount& account) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(account); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetAccount(Session, successCallback, errorCallback); -} - -/** - * Get Users - */ - -void UNakamaClient::GetUsers( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - const TArray& FacebookIds, - FOnGetUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaUserList& UserList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(UserList.Users); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetUsers(Session, UserIds, Usernames, FacebookIds, successCallback, errorCallback); -} - -void UNakamaClient::UpdateAccount( - UNakamaSession *Session, - const FString& Username, - const FString& DisplayName, - const FString& AvatarUrl, - const FString& LanguageTag, - const FString& Location, - const FString& Timezone, - FOnUpdateAccount Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateAccount( - Session, - Username, - DisplayName, - AvatarUrl, - LanguageTag, - Location, - Timezone, - successCallback, - errorCallback - ); -} - -void UNakamaClient::DeleteUser(UNakamaSession* Session, FOnDeleteUser Success, FOnError Error) -{ - auto successCallback = [this, Success]() - { - if (!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if (!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteUser(Session, successCallback, errorCallback); -} - -/** - * Setup Realtime Client (rtClient = Socket) - */ - -UNakamaRealtimeClient* UNakamaClient::SetupRealtimeClient() -{ - UNakamaRealtimeClient* NewClient = NewObject(); - NewClient->Initialize(Hostname, Port, bUseSSL); - NewClient->bIsActive = true; - - return NewClient; -} - -void UNakamaClient::ListMatches( - UNakamaSession *Session, - int32 MinSize, - int32 MaxSize, - int32 Limit, - const FString& Label, - const FString& Query, - bool Authoritative, - FOnMatchlist Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaMatchList& MatchList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(MatchList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLabel = FNakamaUtils::CreateOptional(Label, FString()); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptMinSize = FNakamaUtils::CreateOptional(MinSize, 0); - const auto OptMaxSize = FNakamaUtils::CreateOptional(MaxSize, 0); - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - //const auto OptAuthoritative = FNakamaUtils::CreateOptional(Authoritative, false); - - ListMatches( - Session, - OptMinSize, - OptMaxSize, - OptLimit, - OptLabel, - OptQuery, - Authoritative, - successCallback, - errorCallback - ); -} - - -/** - * Friends System - */ - -void UNakamaClient::GetFriends( - UNakamaSession* Session, - int32 Limit, - ENakamaFriendState State, - const FString& Cursor, - FOnFriendsList Success, - FOnError Error) -{ - ListFriends(Session, Limit, State, Cursor, Success, Error); - -} - -void UNakamaClient::ListFriends( - UNakamaSession* Session, - int32 Limit, - ENakamaFriendState State, - const FString& Cursor, - FOnFriendsList Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaFriendList& Friends) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(Friends); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" Friend States are requested, we return an empty Enum Object - if(State == ENakamaFriendState::ALL) - { - ListFriends(Session, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - ListFriends(Session, OptLimit, State, Cursor, successCallback, errorCallback); - } -} - -void UNakamaClient::AddFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnAddedFriend Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AddFriends(Session, Ids, Usernames, successCallback, errorCallback); -} - -void UNakamaClient::RemoveFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnRemovedFriends Success, - FOnError Error) -{ - DeleteFriends(Session, Ids, Usernames, Success, Error); -} - -void UNakamaClient::DeleteFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnRemovedFriends Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteFriends(Session, Ids, Usernames, successCallback, errorCallback); -} - -void UNakamaClient::BlockFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnBlockedFriends Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - BlockFriends(Session, Ids, Usernames, successCallback, errorCallback); -} - -/** - * Group System - */ - - -void UNakamaClient::CreateGroup( - UNakamaSession* Session, - const FString& GroupName, - const FString& Description, - const FString& AvatarUrl, - const FString& LanguageTag, - bool Open, - int32 MaxMembers, - FOnCreateGroup Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaGroup& Group) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(Group); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptMaxCount = FNakamaUtils::CreateOptional(MaxMembers, 0); - - CreateGroup( - Session, - GroupName, - Description, - AvatarUrl, - LanguageTag, - Open, - OptMaxCount, - successCallback, - errorCallback - ); -} - -void UNakamaClient::ListGroups( - UNakamaSession* Session, - const FString& GroupNameFilter, - int32 Limit, - const FString& Cursor, - FOnGroupsList Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaGroupList& Groups) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(Groups); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ListGroups(Session, GroupNameFilter, Limit, Cursor, successCallback, errorCallback); -} - -void UNakamaClient::JoinGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnJoinedGroup Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - JoinGroup(Session, GroupId, successCallback, errorCallback); -} - -// Note: Does not get members! -void UNakamaClient::ListUserGroups( - UNakamaSession* Session, - const FString& UserId, - int32 Limit, - ENakamaGroupState State, - const FString& Cursor, - FOnUserGroups Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaUserGroupList& UserGroupList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(UserGroupList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" States are requested, we return an empty Enum Object - if(State == ENakamaGroupState::ALL) - { - ListUserGroups(Session, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - ListUserGroups(Session, OptLimit, State, Cursor, successCallback, errorCallback); - } -} - -void UNakamaClient::ListGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - int32 Limit, - ENakamaGroupState State, - const FString& Cursor, - FOnListGroupMembers Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaGroupUsersList& GroupUsersList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(GroupUsersList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - // If "All" States are requested, we return an empty Enum Object - if(State == ENakamaGroupState::ALL) - { - ListGroupUsers(Session, GroupId, OptLimit, {}, Cursor, successCallback, errorCallback); - } - else - { - ListGroupUsers(Session, GroupId, OptLimit, State, Cursor, successCallback, errorCallback); - } - -} - -void UNakamaClient::UpdateGroup( - UNakamaSession* Session, - const FString& GroupId, - const FString& Name, - const FString& Description, - const FString& AvatarUrl, - const FString& LanguageTag, - bool Open, - FOnUpdateGroup Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptName = FNakamaUtils::CreateOptional(Name, FString()); - const auto OptDescription = FNakamaUtils::CreateOptional(Description, FString()); - const auto OptAvatarUrl = FNakamaUtils::CreateOptional(AvatarUrl, FString()); - const auto OptLanguageTag = FNakamaUtils::CreateOptional(LanguageTag, FString()); - - UpdateGroup( - Session, - GroupId, - OptName, - OptDescription, - OptAvatarUrl, - OptLanguageTag, - Open, - successCallback, - errorCallback - ); -} - -void UNakamaClient::LeaveGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnLeaveGroup Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LeaveGroup(Session, GroupId, successCallback, errorCallback); -} - -void UNakamaClient::AddGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnAddGroupUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AddGroupUsers(Session, GroupId, UserIds, successCallback, errorCallback); -} - -void UNakamaClient::PromoteGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnPromoteGroupUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - PromoteGroupUsers(Session, GroupId, UserIds, successCallback, errorCallback); -} - -void UNakamaClient::KickGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnKickGroupUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - KickGroupUsers(Session, GroupId, UserIds, successCallback, errorCallback); -} - -void UNakamaClient::BanGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnBanGroupUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - BanGroupUsers(Session, GroupId, UserIds, successCallback, errorCallback); -} - -void UNakamaClient::DemoteGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnDemoteGroupUsers Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DemoteGroupUsers(Session, GroupId, UserIds, successCallback, errorCallback); -} - -void UNakamaClient::DeleteGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnRemoveGroup Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteGroup(Session, GroupId, successCallback, errorCallback); -} - -/** - * Notification System - */ - -void UNakamaClient::ListNotifications( - UNakamaSession* Session, - int32 Limit, - const FString& Cursor, - FOnListNotifications Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaNotificationList& NotificationList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(NotificationList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCacheableCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - ListNotifications(Session, OptLimit, OptCacheableCursor, successCallback, errorCallback); -} - -void UNakamaClient::DeleteNotifications( - UNakamaSession* Session, - const TArray& NotificationIds, - FOnDeleteNotifications Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteNotifications(Session, NotificationIds, successCallback, errorCallback); -} - -/** - * Storage System - */ - -void UNakamaClient::WriteStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnStorageObjectAcks Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaStoreObjectAcks& StorageObjectAcks) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(StorageObjectAcks); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - WriteStorageObjects(Session, StorageObjectsData, successCallback, errorCallback); -} - -void UNakamaClient::ReadStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnStorageObjectsRead Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaStorageObjectList& StorageObjectList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(StorageObjectList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ReadStorageObjects(Session, StorageObjectsData, successCallback, errorCallback); -} - -void UNakamaClient::ListStorageObjects( - UNakamaSession* Session, - const FString& Collection, - const FString& UserId, - int32 Limit, - const FString& Cursor, - FOnStorageObjectsListed Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaStorageObjectList& StorageObjectList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(StorageObjectList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - if(UserId.IsEmpty()) - { - ListStorageObjects(Session, Collection, OptLimit, OptCursor, successCallback, errorCallback); - } - else - { - ListUsersStorageObjects(Session, Collection, UserId, OptLimit, OptCursor, successCallback, errorCallback); - } -} - -void UNakamaClient::RemoveStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnRemovedStorageObjects Success, - FOnError Error) -{ - DeleteStorageObjects(Session, StorageObjectsData, Success, Error); -} - -void UNakamaClient::DeleteStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnRemovedStorageObjects Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteStorageObjects(Session, StorageObjectsData, successCallback, errorCallback); -} - - -void UNakamaClient::ListParties ( - UNakamaSession* Session, - int32 Limit, - bool Open, - const FString& Query, - const FString& Cursor, - FOnListedParties Success, - FOnError Error -) -{ - auto successCallback = [this, Success](const FNakamaPartyList& PartyList) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(PartyList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - ListParties(Session, OptLimit, Open, OptQuery, OptCursor, successCallback, errorCallback); -} - -/** - * RPC - */ - -bool UNakamaClient::RPC( - UNakamaSession* Session, - const FString& FunctionId, - const FString& Payload, - FOnRPC Success, - FOnError Error) -{ - auto successCallback = [this, Success](FNakamaRPC&& Rpc) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(MoveTemp(Rpc)); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - return RPCm(Session, FunctionId, TOptional(Payload), successCallback, errorCallback); -} - -/** - * RPCHttpKey - */ - -bool UNakamaClient::RPCHttpKey( - const FString& HttpKey, - const FString& FunctionId, - const FString& Payload, - FOnRPC Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaRPC& Rpc) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Success.Broadcast(Rpc); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - return RPC(HttpKey, FunctionId, Payload, successCallback, errorCallback); -} - -/** - * List Channel Messages - */ - -void UNakamaClient::ListChannelMessages( - UNakamaSession* Session, - const FString& ChannelId, - int32 Limit, - const FString& Cursor, - bool Forward, - FOnListChannelMessages Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaChannelMessageList& ChannelMessageList) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(ChannelMessageList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - ListChannelMessages(Session, ChannelId, OptLimit, OptCursor, Forward, successCallback, errorCallback); -} - -/** - * Leaderboards System - */ - -void UNakamaClient::WriteLeaderboardRecord( - UNakamaSession* Session, - const FString& LeaderboardId, - int64 Score, - int64 SubScore, - const FString& Metadata, - FOnWriteLeaderboardRecord Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaLeaderboardRecord& LeaderboardRecord) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(LeaderboardRecord); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptSubScore = FNakamaUtils::CreateOptional(SubScore, 0); - const auto OptMetadata = FNakamaUtils::CreateOptional(Metadata, FString()); - - WriteLeaderboardRecord( - Session, - LeaderboardId, - Score, - OptSubScore, - OptMetadata, - successCallback, - errorCallback - ); -} - -void UNakamaClient::ListLeaderboardRecords( - UNakamaSession* Session, - const FString& LeaderboardId, - const TArray& OwnerIds, - int32 Limit, - const FString& Cursor, - ENakamaLeaderboardListBy ListBy, - FOnListLeaderboardRecords Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaLeaderboardRecordList& LeaderboardRecords) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(LeaderboardRecords); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - if (ListBy == ENakamaLeaderboardListBy::BY_SCORE) - { - ListLeaderboardRecords( - Session, - LeaderboardId, - {}, // None because of listing by score - OptLimit, - OptCursor, - successCallback, - errorCallback - ); - } - else if (ListBy == ENakamaLeaderboardListBy::BY_FRIENDS) - { - ListLeaderboardRecords( - Session, - LeaderboardId, - OwnerIds, // OwnerIds, Can be empty - OptLimit, - OptCursor, - successCallback, - errorCallback - ); - } -} - -void UNakamaClient::ListLeaderboardRecordsAroundOwner( - UNakamaSession* Session, - const FString& LeaderboardId, - const FString& OwnerId, - int32 Limit, - FOnListLeaderboardRecords Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaLeaderboardRecordList& LeaderboardRecords) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(LeaderboardRecords); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - ListLeaderboardRecordsAroundOwner(Session, LeaderboardId, OwnerId, OptLimit, successCallback, errorCallback); -} - -void UNakamaClient::DeleteLeaderboardRecord( - UNakamaSession* Session, - const FString& LeaderboardId, - FOnDeletedLeaderboardRecord Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - DeleteLeaderboardRecord(Session, LeaderboardId, successCallback, errorCallback); -} - -/** - * Tournaments System - */ - -void UNakamaClient::WriteTournamentRecord( - UNakamaSession* Session, - const FString& TournamentId, - int64 Score, - int64 SubScore, - const FString& Metadata, - FOnWriteLeaderboardRecord Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaLeaderboardRecord& LeaderboardRecord) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(LeaderboardRecord); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptSubScore = FNakamaUtils::CreateOptional(SubScore, 0); - const auto OptMetadata = FNakamaUtils::CreateOptional(Metadata, FString()); - - WriteTournamentRecord(Session, TournamentId, Score, OptSubScore, OptMetadata, successCallback, errorCallback); -} - -void UNakamaClient::ListTournamentRecords( - UNakamaSession* Session, - const FString& TournamentId, - int32 Limit, - const FString& Cursor, - const TArray& OwnerIds, - ENakamaLeaderboardListBy ListBy, - FOnListTournamentRecords Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaTournamentRecordList& TournamentRecordList) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(TournamentRecordList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - ListTournamentRecords( - Session, - TournamentId, - OptLimit, - OptCursor, - OwnerIds, - successCallback, - errorCallback - ); -} - -void UNakamaClient::ListTournamentRecordsAroundOwner( - UNakamaSession* Session, - const FString& TournamentId, - const FString& OwnerId, - int32 Limit, - FOnListTournamentRecords Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaTournamentRecordList& TournamentRecordList) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(TournamentRecordList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - - ListTournamentRecordsAroundOwner( - Session, - TournamentId, - OwnerId, - OptLimit, - successCallback, - errorCallback - ); -} - -void UNakamaClient::JoinTournament( - UNakamaSession* Session, - const FString& TournamentId, - FOnJoinedTournament Success, - FOnError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - JoinTournament(Session, TournamentId, successCallback, errorCallback); -} - -void UNakamaClient::ListTournaments( - UNakamaSession* Session, - int32 CategoryStart, - int32 CategoryEnd, - int32 StartTime, - int32 EndTime, - int32 Limit, - const FString& Cursor, - FOnListTournaments Success, - FOnError Error) -{ - auto successCallback = [this, Success](const FNakamaTournamentList& TournamentList) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Success.Broadcast(TournamentList); - }; - - auto errorCallback = [this, Error](const FNakamaError& error) - { - if(!FNakamaUtils::IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - const auto OptCategoryStart = FNakamaUtils::CreateOptional(CategoryStart, 0); - const auto OptCategoryEnd = FNakamaUtils::CreateOptional(CategoryEnd, 0); - const auto OptStartTime = FNakamaUtils::CreateOptional(StartTime, 0); - const auto OptEndTime = FNakamaUtils::CreateOptional(EndTime, 0); - const auto OptLimit = FNakamaUtils::CreateOptional(Limit, 0); - const auto OptCursor = FNakamaUtils::CreateOptional(Cursor, FString()); - - ListTournaments( - Session, - OptCategoryStart, - OptCategoryEnd, - OptStartTime, - OptEndTime, - OptLimit, - OptCursor, - successCallback, - errorCallback - ); -} - -void UNakamaClient::AuthenticateDevice( - const FString& DeviceId, - const TOptional bCreate, - const TOptional& Username, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/device"); - - // Setup the query parameters - TMultiMap QueryParams; - - if (bCreate.IsSet()) - { - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate.GetValue())); - } - - if (Username.IsSet()) - { - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username.GetValue()); - QueryParams.Add(TEXT("username"), EncodedUsername); - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), DeviceId); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateEmail( - const FString& Email, - const FString& Password, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/email"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("email"), Email); - ContentJson->SetStringField(TEXT("password"), Password); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateCustom( - const FString& CustomId, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/custom"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), CustomId); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateApple( - const FString& Token, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/apple"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateFacebook( - const FString& Token, - const FString& Username, - bool bCreate, - bool bImport, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/facebook"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - QueryParams.Add(TEXT("import"), FNakamaUtils::BoolToString(bImport)); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateGoogle( - const FString& Token, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/google"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateGameCenter( - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/gamecenter"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("player_id"), PlayerId); - ContentJson->SetStringField(TEXT("bundle_id"), BundleId); - ContentJson->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); - ContentJson->SetStringField(TEXT("salt"), Salt); - ContentJson->SetStringField(TEXT("signature"), Signature); - ContentJson->SetStringField(TEXT("public_key_url"), PublicKeyUrl); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateSteam( - const FString& Token, - const FString& Username, - bool bCreate, - bool bImport, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/authenticate/steam"); - - // Encode the username - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("create"), FNakamaUtils::BoolToString(bCreate)); - QueryParams.Add(TEXT("username"), EncodedUsername); - QueryParams.Add(TEXT("sync"), FNakamaUtils::BoolToString(bImport)); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - FNakamaUtils::AddVarsToJson(ContentJson, Vars); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AuthenticateRefresh( - UNakamaSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - // NOTE: Endpoint taken from from C++ Client - Docs say: /v2/session/refresh/ - const FString Endpoint = TEXT("/v2/account/session/refresh"); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Session->GetRefreshToken()); - FNakamaUtils::AddVarsToJson(ContentJson, Session->GetVariables()); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), ""); - - // Set the basic authorization header - FNakamaUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(UNakamaSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkDevice( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/device"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), Id); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkEmail( - UNakamaSession* Session, - const FString& Email, - const FString& Password, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/email"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("email"), Email); - ContentJson->SetStringField(TEXT("password"), Password); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkCustom( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/custom"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), Id); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkApple( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/apple"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkFacebook( - UNakamaSession* Session, - const FString& Token, - TOptional bImport, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/facebook"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (bImport.IsSet()) - { - QueryParams.Add(TEXT("import"), FNakamaUtils::BoolToString(bImport.GetValue())); - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkGoogle( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/google"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkGameCenter( - UNakamaSession* Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - TFunction SuccessCallback, TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/gamecenter"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("player_id"), PlayerId); - ContentJson->SetStringField(TEXT("bundle_id"), BundleId); - ContentJson->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); - ContentJson->SetStringField(TEXT("salt"), Salt); - ContentJson->SetStringField(TEXT("signature"), Signature); - ContentJson->SetStringField(TEXT("public_key_url"), PublicKeyUrl); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LinkSteam( - UNakamaSession* Session, - const FString& Token, - //bool bImport, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/link/steam"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - //ContentJson->SetBoolField(TEXT("import"), bImport); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkDevice( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/device"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), Id); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkEmail( - UNakamaSession* Session, - const FString& Email, - const FString& Password, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/email"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("email"), Email); - ContentJson->SetStringField(TEXT("password"), Password); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkCustom( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/custom"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("id"), Id); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkApple( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/apple"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkFacebook( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/facebook"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkGoogle( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/google"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkGameCenter( - UNakamaSession* Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/gamecenter"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("player_id"), PlayerId); - ContentJson->SetStringField(TEXT("bundle_id"), BundleId); - ContentJson->SetNumberField(TEXT("timestamp_seconds"), TimestampSeconds); - ContentJson->SetStringField(TEXT("salt"), Salt); - ContentJson->SetStringField(TEXT("signature"), Signature); - ContentJson->SetStringField(TEXT("public_key_url"), PublicKeyUrl); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UnLinkSteam( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account/unlink/steam"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ImportFacebookFriends( - UNakamaSession* Session, - const FString& Token, - const TOptional bReset, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend/facebook"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // NOTE: Use Query Params for Reset? Docs say json but C++ SDK uses Body/json - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - if (bReset.IsSet()) - { - ContentJson->SetBoolField(TEXT("reset"), bReset.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ImportSteamFriends( - UNakamaSession* Session, - const FString& SteamToken, - const TOptional bReset, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend/steam"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - - if (bReset.IsSet()) - { - QueryParams.Add(TEXT("reset"), FNakamaUtils::BoolToString(bReset.GetValue())); - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), SteamToken); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - - -void UNakamaClient::GetAccount( - UNakamaSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaAccount NakamaAccount = FNakamaAccount(ResponseBody); - SuccessCallback(NakamaAccount); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UpdateAccount( - UNakamaSession* Session, - const TOptional& Username, - const TOptional& DisplayName, - const TOptional& AvatarUrl, - const TOptional& LangTag, - const TOptional& Location, - const TOptional& TimeZone, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - if (Username.IsSet() && !Username.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("username"), Username.GetValue()); - } - if (DisplayName.IsSet() && !DisplayName.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("display_name"), DisplayName.GetValue()); - } - if (AvatarUrl.IsSet() && !AvatarUrl.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("avatar_url"), AvatarUrl.GetValue()); - } - if (LangTag.IsSet() && !LangTag.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("lang_tag"), LangTag.GetValue()); - } - if (Location.IsSet() && !Location.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("location"), Location.GetValue()); - } - if (TimeZone.IsSet() && !TimeZone.GetValue().IsEmpty()) - { - ContentJson->SetStringField(TEXT("timezone"), TimeZone.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - - -void UNakamaClient::DeleteUser( - UNakamaSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/account"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - const FNakamaError Error("Invalid session on DeleteUser call."); - ErrorCallback(Error); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::DEL, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - if (!IsValidLowLevel()) - { - const FNakamaError Error("Invalid http request when lambda executes on DeleteUser call."); - ErrorCallback(Error); - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - if (!HttpRequest->ProcessRequest()) - { - const FNakamaError Error("Failed to process request on DeleteUser call."); - ErrorCallback(Error); - } -} - -void UNakamaClient::GetUsers( - UNakamaSession* Session, - const TArray& UserIds, - const TArray& Usernames, - const TArray& FacebookIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/user"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("ids"), UserId); - } - } - if (Usernames.Num() > 0) - { - for (const FString& Username : Usernames) - { - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - QueryParams.Add(TEXT("usernames"), EncodedUsername); - } - } - if (FacebookIds.Num() > 0) - { - for (const FString& FacebookId : FacebookIds) - { - QueryParams.Add(TEXT("facebook_ids"), FacebookId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaUserList UserList = FNakamaUserList(ResponseBody); - SuccessCallback(UserList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AddFriends( - UNakamaSession* Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("ids"), UserId); - } - } - if (Usernames.Num() > 0) - { - for (const FString& Username : Usernames) - { - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - QueryParams.Add(TEXT("usernames"), EncodedUsername); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DeleteFriends( - UNakamaSession* Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("ids"), UserId); - } - } - if (Usernames.Num() > 0) - { - for (const FString& Username : Usernames) - { - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - QueryParams.Add(TEXT("usernames"), EncodedUsername); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::DEL, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::BlockFriends( - UNakamaSession* Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend/block"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("ids"), UserId); - } - } - if (Usernames.Num() > 0) - { - for (const FString& Username : Usernames) - { - const FString EncodedUsername = FGenericPlatformHttp::UrlEncode(Username); - QueryParams.Add(TEXT("usernames"), EncodedUsername); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListFriends( - UNakamaSession* Session, - const TOptional& Limit, - TOptional State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/friend"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet() && Limit.GetValue() > 0) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (State.IsSet() && State.GetValue() != ENakamaFriendState::ALL) - { - const FString StateString = FNakamaUtils::GetEnumValueAsIntString(State.GetValue()); - QueryParams.Add(TEXT("state"), StateString); - } - if(!Cursor.IsEmpty()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaFriendList FriendsList = FNakamaFriendList(ResponseBody); - SuccessCallback(FriendsList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::CreateGroup( - UNakamaSession *Session, - const FString& Name, - const FString& Description, - const FString& AvatarUrl, - const FString& LangTag, - const bool bOpen, - const TOptional& MaxCount, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/group"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("name"), Name); - ContentJson->SetStringField(TEXT("description"), Description); - ContentJson->SetStringField(TEXT("avatar_url"), AvatarUrl); - ContentJson->SetStringField(TEXT("lang_tag"), LangTag); - ContentJson->SetBoolField(TEXT("open"), bOpen); - - if (MaxCount.IsSet()) - { - ContentJson->SetNumberField(TEXT("max_count"), MaxCount.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaGroup Group = FNakamaGroup(ResponseBody); - SuccessCallback(Group); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DeleteGroup( - UNakamaSession* Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::DEL, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::AddGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/add"), *GroupId); - - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("user_ids"), UserId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TOptional& Limit, - TOptional State, - FString Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/user"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (State.IsSet()) - { - const FString StateString = FNakamaUtils::GetEnumValueAsIntString(State.GetValue()); - QueryParams.Add(TEXT("state"), StateString); - } - if(!Cursor.IsEmpty()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaGroupUsersList GroupUsersList = FNakamaGroupUsersList(ResponseBody); - SuccessCallback(GroupUsersList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::KickGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/kick"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("user_ids"), UserId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::BanGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/ban"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("user_ids"), UserId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::JoinGroup( - UNakamaSession* Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/join"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, {}, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::LeaveGroup( - UNakamaSession* Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/leave"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, {}, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListGroups( - UNakamaSession* Session, - const FString& Name, - int32 Limit, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/group"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (!Name.IsEmpty()) - { - QueryParams.Add(TEXT("name"), Name); - } - if (!Cursor.IsEmpty()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - if (Limit > 0) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit)); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaGroupList GroupList = FNakamaGroupList(ResponseBody); - SuccessCallback(GroupList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -// Used instead of passing in UserId, gets UserId from the Session -void UNakamaClient::ListUserGroups( - UNakamaSession* Session, - const TOptional& Limit, - const TOptional& State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - if(Session && Session->IsValidLowLevel()) - { - ListUserGroups(Session, Session->GetUserId(), Limit, State, Cursor, SuccessCallback, ErrorCallback); - } - else - { - if (ErrorCallback) - { - FNakamaError Error; - Error.Code = ENakamaErrorCode::InvalidArgument; - Error.Message = TEXT("No session"); - - ErrorCallback(Error); - } - } -} - -void UNakamaClient::ListUserGroups( - UNakamaSession* Session, - const FString& UserId, - const TOptional& Limit, - const TOptional& State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/user/%s/group"), *UserId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (State.IsSet()) - { - const FString StateString = FNakamaUtils::GetEnumValueAsIntString(State.GetValue()); - QueryParams.Add(TEXT("state"), StateString); - } - if (!Cursor.IsEmpty()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaUserGroupList UserGroupList = FNakamaUserGroupList(ResponseBody); - SuccessCallback(UserGroupList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::PromoteGroupUsers(UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/promote"), *GroupId); - - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("user_ids"), UserId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DemoteGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s/demote"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (UserIds.Num() > 0) - { - for (const FString& UserId : UserIds) - { - QueryParams.Add(TEXT("user_ids"), UserId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::UpdateGroup( - UNakamaSession* Session, - const FString& GroupId, - const TOptional& Name, - const TOptional& Description, - const TOptional& AvatarUrl, - const TOptional& LangTag, - const TOptional bOpen, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/group/%s"), *GroupId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("group_id"), GroupId); - if (Name.IsSet()) - { - ContentJson->SetStringField(TEXT("name"), Name.GetValue()); - } - if (Description.IsSet()) - { - ContentJson->SetStringField(TEXT("description"), Description.GetValue()); - } - if (AvatarUrl.IsSet()) - { - ContentJson->SetStringField(TEXT("avatar_url"), AvatarUrl.GetValue()); - } - if (LangTag.IsSet()) - { - ContentJson->SetStringField(TEXT("lang_tag"), LangTag.GetValue()); - } - if (bOpen.IsSet()) - { - ContentJson->SetBoolField(TEXT("open"), bOpen.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListLeaderboardRecords( - UNakamaSession* Session, - const FString& LeaderboardId, - const TArray& OwnerIds, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/leaderboard/%s"), *LeaderboardId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - - if (OwnerIds.Num() > 0) - { - for (const FString& UserId : OwnerIds) - { - QueryParams.Add(TEXT("owner_ids"), UserId); - } - } - - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if(Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaLeaderboardRecordList LeaderboardRecords = FNakamaLeaderboardRecordList(ResponseBody); - SuccessCallback(LeaderboardRecords); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListLeaderboardRecordsAroundOwner( - UNakamaSession* Session, - const FString& LeaderboardId, - const FString& OwnerId, - const TOptional& Limit, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/leaderboard/%s/owner/%s"), *LeaderboardId, *OwnerId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaLeaderboardRecordList LeaderboardRecords = FNakamaLeaderboardRecordList(ResponseBody); - SuccessCallback(LeaderboardRecords); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::WriteLeaderboardRecord( - UNakamaSession* Session, - const FString& LeaderboardId, - int64 Score, - const TOptional& Subscore, - const TOptional& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/leaderboard/%s"), *LeaderboardId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - TSharedPtr ContentJson = MakeShareable(new FJsonObject); - ContentJson->SetNumberField(TEXT("score"), Score); - if (Subscore.IsSet()) - { - ContentJson->SetNumberField(TEXT("subscore"), Subscore.GetValue()); - } - if (Metadata.IsSet()) - { - ContentJson->SetStringField(TEXT("metadata"), Metadata.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaLeaderboardRecord LeaderboardRecord = FNakamaLeaderboardRecord(ResponseBody); - SuccessCallback(LeaderboardRecord); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DeleteLeaderboardRecord(UNakamaSession* Session, - const FString& LeaderboardId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/leaderboard/%s"), *LeaderboardId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::DEL, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListMatches(UNakamaSession* Session, - const TOptional& MinSize, - const TOptional& MaxSize, - const TOptional& Limit, - const TOptional& Label, - const TOptional& Query, - const TOptional Authoritative, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/match"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (MinSize.IsSet()) - { - QueryParams.Add(TEXT("min_size"), FString::FromInt(MinSize.GetValue())); - } - if (MaxSize.IsSet()) - { - QueryParams.Add(TEXT("max_size"), FString::FromInt(MaxSize.GetValue())); - } - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Label.IsSet()) - { - const FString EncodedLabel = FGenericPlatformHttp::UrlEncode(Label.GetValue()); - QueryParams.Add(TEXT("label"), EncodedLabel); - } - if (Query.IsSet()) - { - const FString EncodedQuery = FGenericPlatformHttp::UrlEncode(Query.GetValue()); - QueryParams.Add(TEXT("query"), EncodedQuery); - } - if (Authoritative.IsSet()) - { - QueryParams.Add(TEXT("authoritative"), FNakamaUtils::BoolToString(Authoritative.GetValue())); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaMatchList MatchList = FNakamaMatchList(ResponseBody); - SuccessCallback(MatchList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListNotifications(UNakamaSession* Session, - const TOptional& Limit, - const TOptional& CacheableCursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/notification"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (CacheableCursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(CacheableCursor.GetValue()); - QueryParams.Add(TEXT("cacheable_cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaNotificationList NotificationList = FNakamaNotificationList(ResponseBody); - SuccessCallback(NotificationList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DeleteNotifications( - UNakamaSession* Session, - const TArray& NotificationIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/notification"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (NotificationIds.Num() > 0) - { - for (const FString& NotificationId : NotificationIds) - { - QueryParams.Add(TEXT("ids"), NotificationId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::DEL, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListChannelMessages(UNakamaSession* Session, - const FString& ChannelId, - const TOptional& Limit, - const TOptional& Cursor, - const TOptional Forward, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/channel/%s"), *ChannelId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - if (Forward.IsSet()) - { - QueryParams.Add(TEXT("forward"), FNakamaUtils::BoolToString(Forward.GetValue())); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaChannelMessageList ChannelMessageList = FNakamaChannelMessageList(ResponseBody); - SuccessCallback(ChannelMessageList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListTournaments(UNakamaSession* Session, - const TOptional& CategoryStart, - const TOptional& CategoryEnd, - const TOptional& StartTime, - const TOptional& EndTime, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/tournament"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (CategoryStart.IsSet()) - { - QueryParams.Add(TEXT("category_start"), FString::FromInt(CategoryStart.GetValue())); - } - if (CategoryEnd.IsSet()) - { - QueryParams.Add(TEXT("category_end"), FString::FromInt(CategoryEnd.GetValue())); - } - if (StartTime.IsSet()) - { - QueryParams.Add(TEXT("start_time"), FString::FromInt(StartTime.GetValue())); - } - if (EndTime.IsSet()) - { - QueryParams.Add(TEXT("end_time"), FString::FromInt(EndTime.GetValue())); - } - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaTournamentList TournamentList = FNakamaTournamentList(ResponseBody); - SuccessCallback(TournamentList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListTournamentRecords(UNakamaSession* Session, - const FString& TournamentId, - const TOptional& Limit, - const TOptional& Cursor, - const TArray& OwnerIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/tournament/%s"), *TournamentId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - if (OwnerIds.Num() > 0) - { - for (const FString& OwnerId : OwnerIds) - { - QueryParams.Add(TEXT("owner_ids"), OwnerId); - } - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaTournamentRecordList TournamentRecordList = FNakamaTournamentRecordList(ResponseBody); - SuccessCallback(TournamentRecordList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListTournamentRecordsAroundOwner( - UNakamaSession* Session, const FString& TournamentId, - const FString& OwnerId, const TOptional& Limit, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/tournament/%s/owner/%s"), *TournamentId, *OwnerId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaTournamentRecordList TournamentRecordList = FNakamaTournamentRecordList(ResponseBody); - SuccessCallback(TournamentRecordList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::WriteTournamentRecord(UNakamaSession* Session, - const FString& TournamentId, int64 Score, - const TOptional& Subscore, - const TOptional& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/tournament/%s"), *TournamentId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - TSharedPtr ContentJson = MakeShareable(new FJsonObject); - ContentJson->SetNumberField(TEXT("score"), Score); - if (Subscore.IsSet()) - { - ContentJson->SetNumberField(TEXT("subscore"), Subscore.GetValue()); - } - if (Metadata.IsSet()) - { - ContentJson->SetStringField(TEXT("metadata"), Metadata.GetValue()); - } - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaLeaderboardRecord TournamentRecord = FNakamaLeaderboardRecord(ResponseBody); - SuccessCallback(TournamentRecord); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::JoinTournament( - UNakamaSession* Session, - const FString& TournamentId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/tournament/%s/join"), *TournamentId); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListStorageObjects(UNakamaSession* Session, - const FString& Collection, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/storage/%s"), *Collection); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - //QueryParams.Add(TEXT("user_id"), Session->GetUserId()); - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaStorageObjectList StorageObjectList = FNakamaStorageObjectList(ResponseBody); - SuccessCallback(StorageObjectList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListUsersStorageObjects(UNakamaSession* Session, - const FString& Collection, - const FString& UserId, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString::Printf(TEXT("/v2/storage/%s"), *Collection); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add( TEXT("user_id"), UserId); - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaStorageObjectList StorageObjectList = FNakamaStorageObjectList(ResponseBody); - SuccessCallback(StorageObjectList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::WriteStorageObjects( - UNakamaSession* Session, - const TArray& Objects, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/storage"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request body - TArray> ObjectsJson; - for (const FNakamaStoreObjectWrite& Object : Objects) - { - TSharedPtr ObjectJson = MakeShareable(new FJsonObject()); - ObjectJson->SetStringField(TEXT("collection"), Object.Collection); - ObjectJson->SetStringField(TEXT("key"), Object.Key); - ObjectJson->SetStringField(TEXT("value"), Object.Value); - ObjectJson->SetStringField(TEXT("version"), Object.Version); - - // Note: Should these be optional? - const FString PermissionRead = FNakamaUtils::GetEnumValueAsIntString(Object.PermissionRead); - ObjectJson->SetStringField(TEXT("permission_read"), PermissionRead); - - const FString PermissionWrite = FNakamaUtils::GetEnumValueAsIntString(Object.PermissionWrite); - ObjectJson->SetStringField(TEXT("permission_write"), PermissionWrite); - - ObjectsJson.Add(MakeShareable(new FJsonValueObject(ObjectJson))); - } - - TSharedPtr RequestBodyJson = MakeShareable(new FJsonObject()); - RequestBodyJson->SetArrayField(TEXT("objects"), ObjectsJson); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(RequestBodyJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaStoreObjectAcks StoreObjectAcks = FNakamaStoreObjectAcks(ResponseBody); - SuccessCallback(StoreObjectAcks); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ReadStorageObjects( - UNakamaSession* Session, - const TArray& ObjectIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/storage"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request body - TArray> ObjectsJson; - for (const FNakamaReadStorageObjectId& Object : ObjectIds) - { - TSharedPtr ObjectJson = MakeShareable(new FJsonObject()); - ObjectJson->SetStringField(TEXT("collection"), Object.Collection); - ObjectJson->SetStringField(TEXT("key"), Object.Key); - ObjectJson->SetStringField(TEXT("user_id"), Object.UserId); - - ObjectsJson.Add(MakeShareable(new FJsonValueObject(ObjectJson))); - } - - TSharedPtr RequestBodyJson = MakeShareable(new FJsonObject()); - RequestBodyJson->SetArrayField(TEXT("object_ids"), ObjectsJson); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(RequestBodyJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaStorageObjectList StorageObjectList = FNakamaStorageObjectList(ResponseBody); - SuccessCallback(StorageObjectList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::DeleteStorageObjects( - UNakamaSession* Session, - const TArray& ObjectIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/storage/delete"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request body - TArray> ObjectsJson; - for (const FNakamaDeleteStorageObjectId& Object : ObjectIds) - { - TSharedPtr ObjectJson = MakeShareable(new FJsonObject()); - ObjectJson->SetStringField(TEXT("collection"), Object.Collection); - ObjectJson->SetStringField(TEXT("key"), Object.Key); - ObjectJson->SetStringField(TEXT("version"), Object.Version); - - ObjectsJson.Add(MakeShareable(new FJsonValueObject(ObjectJson))); - } - - TSharedPtr RequestBodyJson = MakeShareable(new FJsonObject()); - RequestBodyJson->SetArrayField(TEXT("object_ids"), ObjectsJson); - - // Serialize the request content - FString Content; - if (!FNakamaUtils::SerializeJsonObject(RequestBodyJson, Content)) - { - // Handle JSON serialization failure - FNakamaUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ENakamaRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void UNakamaClient::ListParties ( - UNakamaSession* Session, - const TOptional& Limit, - const TOptional& Open, - const TOptional& Query, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback -) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v2/party"); - - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - if (Limit.IsSet()) - { - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit.GetValue())); - } - if (Open.IsSet()) - { - QueryParams.Add(TEXT("open"), FNakamaUtils::BoolToString(Open.GetValue())); - } - if (Query.IsSet()) - { - const FString EncodedQuery = FGenericPlatformHttp::UrlEncode(Query.GetValue()); - QueryParams.Add(TEXT("query"), EncodedQuery); - } - if (Cursor.IsSet()) - { - const FString EncodedCursor = FGenericPlatformHttp::UrlEncode(Cursor.GetValue()); - QueryParams.Add(TEXT("cursor"), EncodedCursor); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FNakamaUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FNakamaPartyList PartyList = FNakamaPartyList(ResponseBody); - SuccessCallback(PartyList); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FNakamaError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FNakamaError RequestError = FNakamaUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -bool UNakamaClient::RPC( - UNakamaSession* Session, - const FString& Id, - const TOptional& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return false; - } - - // Call the SendRPC function - return SendRPC(Session, Id, Payload, {}, SuccessCallback, ErrorCallback); -} - -bool UNakamaClient::RPC( - const FString& HttpKey, - const FString& Id, - const FString& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - TMultiMap QueryParams; - //QueryParams.Add(TEXT("http_key"), HttpKey); - const FString EncodedHttpKey = FGenericPlatformHttp::UrlEncode(HttpKey); - QueryParams.Add(TEXT("http_key"), EncodedHttpKey); - - - // Sends Empty Session - return SendRPC({}, Id, TOptional(Payload), QueryParams, SuccessCallback, ErrorCallback); -} - -bool UNakamaClient::RPCm( - UNakamaSession* Session, - const FString& Id, - TOptional&& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Verify the session - if (!FNakamaUtils::IsSessionValid(Session, ErrorCallback)) - { - return false; - } - - // Call the SendRPC function - return SendRPCm(Session, Id, MoveTemp(Payload), {}, SuccessCallback, ErrorCallback); -} - -bool UNakamaClient::RPCm(const FString& HttpKey, const FString& Id, FString&& Payload, - TFunction SuccessCallback, TFunction ErrorCallback) -{ - TMultiMap QueryParams; - //QueryParams.Add(TEXT("http_key"), HttpKey); - const FString EncodedHttpKey = FGenericPlatformHttp::UrlEncode(HttpKey); - QueryParams.Add(TEXT("http_key"), EncodedHttpKey); - - - // Sends Empty Session - return SendRPCm({}, Id, TOptional(MoveTemp(Payload)), QueryParams, SuccessCallback, ErrorCallback); -} - -// End of TFunctions - -FString UNakamaClient::ConstructURL(const FString& Endpoint) -{ - FString Protocol = bUseSSL ? TEXT("https") : TEXT("http"); - FString URL = FString::Printf(TEXT("%s://%s:%d%s"), *Protocol, *Hostname, Port, *Endpoint); - - return URL; -} - -TSharedRef UNakamaClient::MakeRequest(const FString& Endpoint, - const FString& Content, ENakamaRequestMethod RequestMethod, const TMultiMap& QueryParams, - const FString& SessionToken) -{ - // Append query parameters to the endpoint - FString ModifiedEndpoint = Endpoint; - if (QueryParams.Num() > 0) - { - FString QueryString = FNakamaUtils::BuildQueryString(QueryParams); - ModifiedEndpoint += "?" + QueryString; - } - - // Construct the URL - FString URL = ConstructURL(ModifiedEndpoint); - - return FNakamaUtils::MakeRequest(URL, Content, RequestMethod, SessionToken, Timeout); -} - -bool UNakamaClient::IsClientValid() const -{ - return IsValidLowLevel(); - //return IsValid(this); -} - -bool UNakamaClient::SendRPC(UNakamaSession* Session, const FString& Id, const TOptional& Payload, - TMultiMap QueryParams, TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString("/v2/rpc/") + Id; - - if (Payload.IsSet() && !Payload.GetValue().IsEmpty()) - { - // This part is important - const FString EscapedPayload = EscapeJsonString(Payload.GetValue()); - - FString SessionToken; - if (Session != nullptr) - { - SessionToken = Session->GetAuthToken(); - } - - // Make the POST request with payload - const auto HttpRequest = MakeRequest(Endpoint, EscapedPayload, ENakamaRequestMethod::POST, QueryParams, SessionToken); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - FNakamaUtils::ProcessRequestCompleteMove(Request, Response, bSuccess, [SuccessCallback](FString&& ResponseBody) { - if (SuccessCallback) - { - SuccessCallback(FNakamaRPC(MoveTemp(ResponseBody))); - } - }, [ErrorCallback](const FNakamaError& Error) { - if (ErrorCallback) - { - ErrorCallback(Error); - } - }); - }); - - // Process the request - return HttpRequest->ProcessRequest(); - } - else - { - // Add the RPC ID to the query parameters - QueryParams.Add(TEXT("id"), Id); - - FString SessionToken; - if (Session != nullptr) - { - SessionToken = Session->GetAuthToken(); - } - - // Make the GET request without payload - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, SessionToken); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - FNakamaUtils::ProcessRequestCompleteMove(Request, Response, bSuccess, [SuccessCallback](FString&& ResponseBody) { - if (SuccessCallback) - { - SuccessCallback(FNakamaRPC(MoveTemp(ResponseBody))); - } - }, [ErrorCallback](const FNakamaError& Error) { - if (ErrorCallback) - { - ErrorCallback(Error); - } - }); - }); - - // Process the request - return HttpRequest->ProcessRequest(); - } -} - -bool UNakamaClient::SendRPCm(UNakamaSession* Session, const FString& Id, const TOptional& Payload, - TMultiMap QueryParams, TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = FString("/v2/rpc/") + Id; - - if (Payload.IsSet() && !Payload.GetValue().IsEmpty()) - { - // This part is important - const FString EscapedPayload = EscapeJsonString(Payload.GetValue()); - - FString SessionToken; - if (Session != nullptr) - { - SessionToken = Session->GetAuthToken(); - } - - // Make the POST request with payload - const auto HttpRequest = MakeRequest(Endpoint, EscapedPayload, ENakamaRequestMethod::POST, QueryParams, SessionToken); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - FNakamaUtils::ProcessRequestCompleteMove(Request, Response, bSuccess, [SuccessCallback](FString&& ResponseBody) { - if (SuccessCallback) - { - SuccessCallback(FNakamaRPC(MoveTemp(ResponseBody))); - } - }, [ErrorCallback](const FNakamaError& Error) { - if (ErrorCallback) - { - ErrorCallback(Error); - } - }); - }); - - // Process the request - return HttpRequest->ProcessRequest(); - } - else - { - // Add the RPC ID to the query parameters - QueryParams.Add(TEXT("id"), Id); - - FString SessionToken; - if (Session != nullptr) - { - SessionToken = Session->GetAuthToken(); - } - - // Make the GET request without payload - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ENakamaRequestMethod::GET, QueryParams, SessionToken); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - FNakamaUtils::ProcessRequestCompleteMove(Request, Response, bSuccess, [SuccessCallback](FString&& ResponseBody) { - if (SuccessCallback) - { - SuccessCallback(FNakamaRPC(MoveTemp(ResponseBody))); - } - }, [ErrorCallback](const FNakamaError& Error) { - if (ErrorCallback) - { - ErrorCallback(Error); - } - }); - }); - - // Process the request - return HttpRequest->ProcessRequest(); - } -} - -void UNakamaClient::CancelAllRequests() -{ - if(!IsValidLowLevel()) - { - return; - } - - // Lock the mutex to protect access to ActiveRequests - FScopeLock Lock(&ActiveRequestsMutex); - - // Iterate over the active requests and cancel each one - for (const FHttpRequestPtr& Request : ActiveRequests) - { - Request->OnProcessRequestComplete().Unbind(); - Request->CancelRequest(); - } - - // Clear the ActiveRequests array - ActiveRequests.Empty(); -} - diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaError.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaError.cpp deleted file mode 100644 index 0740c1753..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaError.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaError.h" -#include "NakamaUtils.h" - -ENakamaErrorCode FNakamaError::ConvertNakamaErrorCode(int32 CodeValue) -{ - switch (CodeValue) - { - case 0: - return ENakamaErrorCode::Ok; - case 1: - return ENakamaErrorCode::Cancelled; - case 2: - return ENakamaErrorCode::Unknown; - case 3: - return ENakamaErrorCode::InvalidArgument; - case 4: - return ENakamaErrorCode::DeadlineExceeded; - case 5: - return ENakamaErrorCode::NotFound; - case 6: - return ENakamaErrorCode::AlreadyExists; - case 7: - return ENakamaErrorCode::PermissionDenied; - case 8: - return ENakamaErrorCode::ResourceExhausted; - case 9: - return ENakamaErrorCode::FailedPrecondition; - case 10: - return ENakamaErrorCode::Aborted; - case 11: - return ENakamaErrorCode::OutOfRange; - case 12: - return ENakamaErrorCode::Unimplemented; - case 13: - return ENakamaErrorCode::Internal; - case 14: - return ENakamaErrorCode::Unavailable; - case 15: - return ENakamaErrorCode::DataLoss; - case 16: - return ENakamaErrorCode::Unauthenticated; - default: - return ENakamaErrorCode::Unknown; - } -} - -FNakamaError::FNakamaError(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - if (JsonObject->HasField(TEXT("message"))) - { - Message = JsonObject->GetStringField(TEXT("message")); - } - else - { - Message = TEXT("Invalid or missing 'message' field"); - } - - int32 CodeValue; - if (JsonObject->TryGetNumberField(TEXT("code"), CodeValue)) - { - //Code = static_cast(CodeValue); - Code = ConvertNakamaErrorCode(CodeValue); - } - else - { - Code = ENakamaErrorCode::Unknown; - } - } - else - { - Message = TEXT("Failed to parse JSON"); - Code = ENakamaErrorCode::Unknown; - } -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaFriend.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaFriend.cpp deleted file mode 100644 index 371eee56c..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaFriend.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaFriend.h" -#include "NakamaUtils.h" - - -FNakamaFriend::FNakamaFriend(const FString& JsonString) : FNakamaFriend(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaFriend::FNakamaFriend(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - const TSharedPtr* UserJsonObject; - if (JsonObject->TryGetObjectField(TEXT("user"), UserJsonObject)) - { - NakamaUser = FNakamaUser(*UserJsonObject); - } - - FString StateString; - if (JsonObject->TryGetStringField(TEXT("state"), StateString)) - { - UserState = GetFriendStateFromString(StateString); - } - - FString UpdateTimeString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdateTimeString)) - { - FDateTime::ParseIso8601(*UpdateTimeString, UpdateTime); - } - } -} - -FNakamaFriend::FNakamaFriend(): UserState(ENakamaFriendState::FRIEND), UpdateTime(FDateTime::MinValue()) -{ - -} - -ENakamaFriendState FNakamaFriend::GetFriendStateFromString(const FString& StateString) -{ - if (StateString == "0") - return ENakamaFriendState::FRIEND; - else if (StateString == "1") - return ENakamaFriendState::INVITE_SENT; - else if (StateString == "2") - return ENakamaFriendState::INVITE_RECEIVED; - else if (StateString == "3") - return ENakamaFriendState::BLOCKED; - - return ENakamaFriendState::FRIEND; // Default to FRIEND state if the string doesn't match any known value -} - -FNakamaFriendList::FNakamaFriendList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* FriendsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("friends"), FriendsJsonArray)) - { - for (const TSharedPtr& FriendJsonValue : *FriendsJsonArray) - { - if (TSharedPtr FriendJsonObject = FriendJsonValue->AsObject()) - { - FNakamaFriend Friend(FriendJsonObject); - NakamaUsers.Add(Friend); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - -FNakamaFriendList::FNakamaFriendList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaGroup.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaGroup.cpp deleted file mode 100644 index 9bea8ba71..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaGroup.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaGroup.h" -#include "NakamaUtils.h" - -ENakamaGroupState GetGroupStateFromString(const FString& StateString) -{ - if (StateString == "0") - { - return ENakamaGroupState::SUPERADMIN; - } - else if (StateString == "1") - { - return ENakamaGroupState::ADMIN; - } - else if (StateString == "2") - { - return ENakamaGroupState::MEMBER; - } - else if (StateString == "3") - { - return ENakamaGroupState::JOIN_REQUEST; - } - else - { - return ENakamaGroupState::SUPERADMIN; - } -} - - -FNakamaGroup::FNakamaGroup(const FString& JsonString) : FNakamaGroup(FNakamaUtils::DeserializeJsonObject(JsonString)) -{} - -FNakamaGroup::FNakamaGroup(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("creator_id"), CreatorId); - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("description"), Description); - JsonObject->TryGetStringField(TEXT("lang_tag"), Language); - JsonObject->TryGetStringField(TEXT("metadata"), MetaData); - JsonObject->TryGetStringField(TEXT("avatar_url"), AvatarUrl); - - JsonObject->TryGetBoolField(TEXT("open"), open); - - double Tmp; - if (JsonObject->TryGetNumberField(TEXT("edge_count"), Tmp)) - { - EdgeCount = static_cast(Tmp); - } - if (JsonObject->TryGetNumberField(TEXT("max_count"), Tmp)) - { - MaxCount = static_cast(Tmp); - } - - FString CreateTimeString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreateTimeString)) - { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - FString UpdateTimeString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdateTimeString)) - { - FDateTime::ParseIso8601(*UpdateTimeString, UpdateTime); - } - } -} - - -FNakamaGroupUser::FNakamaGroupUser(const FString& JsonString) : FNakamaGroupUser(FNakamaUtils::DeserializeJsonObject(JsonString)) -{ -} - -FNakamaGroupUser::FNakamaGroupUser(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - const TSharedPtr* UserJsonObject; - if (JsonObject->TryGetObjectField(TEXT("user"), UserJsonObject)) - { - User = FNakamaUser(*UserJsonObject); - } - - FString StateString; - if (JsonObject->TryGetStringField(TEXT("state"), StateString)) - { - State = GetGroupStateFromString(StateString); - } - } -} - -FNakamaGroupUser::FNakamaGroupUser(): State() -{ - -} - -FNakamaGroupUsersList::FNakamaGroupUsersList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* GroupUsersJsonArray; - if (JsonObject->TryGetArrayField(TEXT("group_users"), GroupUsersJsonArray)) - { - for (const TSharedPtr& GroupUserJsonValue : *GroupUsersJsonArray) - { - if (TSharedPtr GroupUserJsonObject = GroupUserJsonValue->AsObject()) - { - GroupUsers.Add(FNakamaGroupUser(GroupUserJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - -FNakamaGroupList::FNakamaGroupList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* GroupsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("groups"), GroupsJsonArray)) - { - for (const TSharedPtr& GroupJsonValue : *GroupsJsonArray) - { - if (TSharedPtr GroupJsonObject = GroupJsonValue->AsObject()) - { - Groups.Add(FNakamaGroup(GroupJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - - -FNakamaUserGroup::FNakamaUserGroup(const FString& JsonString) : FNakamaUserGroup(FNakamaUtils::DeserializeJsonObject(JsonString)) -{ -} - -FNakamaUserGroup::FNakamaUserGroup(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - if (JsonObject->HasField(TEXT("group")) && JsonObject->GetField(TEXT("group"))) - { - const TSharedPtr* GroupJsonObject; - if (JsonObject->TryGetObjectField(TEXT("group"), GroupJsonObject)) - { - Group = FNakamaGroup(*GroupJsonObject); - } - } - - FString StateString; - if (JsonObject->TryGetStringField(TEXT("state"), StateString)) - { - State = GetGroupStateFromString(StateString); - } - } -} - -FNakamaUserGroup::FNakamaUserGroup(): State() -{ - -} - -FNakamaUserGroupList::FNakamaUserGroupList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* UserGroupsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("user_groups"), UserGroupsJsonArray)) - { - for (const TSharedPtr& UserGroupJsonValue : *UserGroupsJsonArray) - { - if (TSharedPtr UserGroupJsonObject = UserGroupJsonValue->AsObject()) - { - UserGroups.Add(FNakamaUserGroup(UserGroupJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - -FNakamaUserGroupList::FNakamaUserGroupList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaLeaderboard.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaLeaderboard.cpp deleted file mode 100644 index 7e763208c..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaLeaderboard.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaLeaderboard.h" -#include "NakamaUtils.h" - -FNakamaLeaderboardRecord::FNakamaLeaderboardRecord(const FString& JsonString) : FNakamaLeaderboardRecord(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaLeaderboardRecord::FNakamaLeaderboardRecord(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("leaderboard_id"), LeaderboardId); - JsonObject->TryGetStringField(TEXT("owner_id"), OwnerId); - JsonObject->TryGetStringField(TEXT("username"), Username); - JsonObject->TryGetNumberField(TEXT("score"), Score); - JsonObject->TryGetNumberField(TEXT("subscore"), SubScore); - JsonObject->TryGetNumberField(TEXT("num_score"), NumScore); - JsonObject->TryGetNumberField(TEXT("max_num_score"), MaxNumScore); - JsonObject->TryGetStringField(TEXT("metadata"), Metadata); - - FString CreateTimeString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreateTimeString)) - { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - - FString UpdateTimeString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdateTimeString)) - { - FDateTime::ParseIso8601(*UpdateTimeString, UpdateTime); - } - - FString ExpiryTimeString; - if (JsonObject->TryGetStringField(TEXT("expiry_time"), ExpiryTimeString)) - { - FDateTime::ParseIso8601(*ExpiryTimeString, ExpiryTime); - } - - JsonObject->TryGetNumberField(TEXT("rank"), Rank); - } -} - -FNakamaLeaderboardRecord::FNakamaLeaderboardRecord() -{ - -} - -FNakamaLeaderboardRecordList::FNakamaLeaderboardRecordList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* RecordsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("records"), RecordsJsonArray)) - { - for (const TSharedPtr& RecordJsonValue : *RecordsJsonArray) - { - if (TSharedPtr RecordJsonObject = RecordJsonValue->AsObject()) - { - Records.Add(FNakamaLeaderboardRecord(RecordJsonObject)); - } - } - } - - const TArray>* OwnerRecordsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("owner_records"), OwnerRecordsJsonArray)) - { - for (const TSharedPtr& OwnerRecordJsonValue : *OwnerRecordsJsonArray) - { - if (TSharedPtr OwnerRecordJsonObject = OwnerRecordJsonValue->AsObject()) - { - OwnerRecords.Add(FNakamaLeaderboardRecord(OwnerRecordJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("next_cursor"), NextCursor); - JsonObject->TryGetStringField(TEXT("prev_cursor"), PrevCursor); - } -} - -FNakamaLeaderboardRecordList::FNakamaLeaderboardRecordList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaLogger.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaLogger.cpp deleted file mode 100644 index ce74611d5..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaLogger.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaLogger.h" - -DEFINE_LOG_CATEGORY(LogNakamaUnreal); - -ENakamaLogLevel UNakamaLogger::CurrentLogLevel = ENakamaLogLevel::Info; -bool UNakamaLogger::bLoggingEnabled = false; - -UNakamaLogger::UNakamaLogger() -{ - -} - -void UNakamaLogger::SetLogLevel(ENakamaLogLevel InLogLevel) -{ - CurrentLogLevel = InLogLevel; -} - -bool UNakamaLogger::IsLoggable(ENakamaLogLevel InLogLevel) -{ - // Logging Disabled check - if (!bLoggingEnabled) - return false; - - // Debug - if(CurrentLogLevel == ENakamaLogLevel::Debug) - { - if(InLogLevel == ENakamaLogLevel::Debug) - return true; - - if(InLogLevel == ENakamaLogLevel::Info) - return true; - - if(InLogLevel == ENakamaLogLevel::Warn) - return true; - - if(InLogLevel == ENakamaLogLevel::Error) - return true; - - if(InLogLevel == ENakamaLogLevel::Fatal) - return true; - } - - // Info - if(CurrentLogLevel == ENakamaLogLevel::Info) - { - if(InLogLevel == ENakamaLogLevel::Info) - return true; - - if(InLogLevel == ENakamaLogLevel::Warn) - return true; - - if(InLogLevel == ENakamaLogLevel::Error) - return true; - - if(InLogLevel == ENakamaLogLevel::Fatal) - return true; - } - - // Warn - if(CurrentLogLevel == ENakamaLogLevel::Warn) - { - if(InLogLevel == ENakamaLogLevel::Warn) - return true; - - if(InLogLevel == ENakamaLogLevel::Error) - return true; - - if(InLogLevel == ENakamaLogLevel::Fatal) - return true; - } - - // Error - if(CurrentLogLevel == ENakamaLogLevel::Error) - { - if(InLogLevel == ENakamaLogLevel::Error) - return true; - - if(InLogLevel == ENakamaLogLevel::Fatal) - return true; - } - - // Fatal - if(CurrentLogLevel == ENakamaLogLevel::Fatal) - { - if(InLogLevel == ENakamaLogLevel::Fatal) - return true; - } - - return false; -} - -void UNakamaLogger::Log(ENakamaLogLevel InLogLevel, const FString& Message) -{ - if (IsLoggable(InLogLevel)) - { - switch (InLogLevel) - { - case ENakamaLogLevel::Debug: - UE_LOG(LogNakamaUnreal, Verbose, TEXT("%s"), *Message); - break; - case ENakamaLogLevel::Info: - UE_LOG(LogNakamaUnreal, Display, TEXT("%s"), *Message); - break; - case ENakamaLogLevel::Warn: - UE_LOG(LogNakamaUnreal, Warning, TEXT("%s"), *Message); - break; - case ENakamaLogLevel::Error: - UE_LOG(LogNakamaUnreal, Error, TEXT("%s"), *Message); - break; - case ENakamaLogLevel::Fatal: - UE_LOG(LogNakamaUnreal, Fatal, TEXT("%s"), *Message); - break; - default: - break; - } - } -} - -void UNakamaLogger::EnableLogging(bool bEnable) -{ - bLoggingEnabled = bEnable; -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaMatch.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaMatch.cpp deleted file mode 100644 index fb2bb1be1..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaMatch.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaMatch.h" -#include "NakamaUtils.h" - -FNakamaMatch::FNakamaMatch(const FString& JsonString) : FNakamaMatch(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaMatch::FNakamaMatch(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - // Try to get the "match" object from the parsed JSON - const TSharedPtr* MatchObjectPtr = nullptr; - JsonObject->TryGetObjectField(TEXT("match"), MatchObjectPtr); - - // If "match" object is not found, use the root object - TSharedPtr MatchObject = MatchObjectPtr ? *MatchObjectPtr : JsonObject; - - // NOTE: Server can respond with 'tick_rate' and 'handler_name' as well - - // Extract and assign the individual fields - MatchObject->TryGetStringField(TEXT("match_id"), MatchId); - MatchObject->TryGetBoolField(TEXT("authoritative"), Authoritative); - MatchObject->TryGetStringField(TEXT("label"), Label); - MatchObject->TryGetNumberField(TEXT("size"), Size); - - const TArray>* JoinsJsonArray; - if (MatchObject->TryGetArrayField(TEXT("presences"), JoinsJsonArray)) - { - for (const TSharedPtr& UserPresence : *JoinsJsonArray) - { - if (TSharedPtr UserPresenceJsonObject = UserPresence->AsObject()) - { - Pressences.Add(FNakamaUserPresence(UserPresenceJsonObject)); - } - } - } - - // Try to get the "self" object from the match object - const TSharedPtr* SelfObjectPtr; - if (MatchObject->TryGetObjectField(TEXT("self"), SelfObjectPtr)) - { - Me = FNakamaUserPresence(*SelfObjectPtr); - } - } -} - -FNakamaMatchData::FNakamaMatchData(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("match_id"), MatchId); - - const TSharedPtr* PresenceJsonObject; - if (JsonObject->TryGetObjectField(TEXT("presence"), PresenceJsonObject)) - { - Presence = FNakamaUserPresence(*PresenceJsonObject); - } - - JsonObject->TryGetNumberField(TEXT("op_code"), OpCode); - - if(JsonObject->HasField(TEXT("data"))) - { - FNakamaUtils::Base64Decode(JsonObject->GetStringField(TEXT("data")), Data); - } - - } -} - -FNakamaMatchData::FNakamaMatchData(): OpCode(0) -{ -} - -FNakamaMatchList::FNakamaMatchList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* MatchesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("matches"), MatchesJsonArray)) - { - for (const TSharedPtr& MatchJsonValue : *MatchesJsonArray) - { - if (TSharedPtr MatchJsonObject = MatchJsonValue->AsObject()) - { - Matches.Add(FNakamaMatch(MatchJsonObject)); - } - } - } - } -} - -FNakamaMatchList::FNakamaMatchList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaMatchTypes.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaMatchTypes.cpp deleted file mode 100644 index 49f83f1fd..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaMatchTypes.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaMatchTypes.h" -#include "NakamaUtils.h" - -FNakamaMatchmakerUser::FNakamaMatchmakerUser(const FString& JsonString) : FNakamaMatchmakerUser(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaMatchmakerUser::FNakamaMatchmakerUser(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - const TSharedPtr* PresenceObject; - if (JsonObject->TryGetObjectField(TEXT("presence"), PresenceObject)) - { - Presence = FNakamaUserPresence(*PresenceObject); - } - - const TSharedPtr* StringPropertiesObject; - if (JsonObject->TryGetObjectField(TEXT("string_properties"), StringPropertiesObject)) - { - for (const auto& Entry : (*StringPropertiesObject)->Values) - { - StringProperties.Add(Entry.Key, Entry.Value->AsString()); - } - } - - const TSharedPtr* NumericPropertiesObject; - if (JsonObject->TryGetObjectField(TEXT("numeric_properties"), NumericPropertiesObject)) - { - for (const auto& Entry : (*NumericPropertiesObject)->Values) - { - NumericProperties.Add(Entry.Key, Entry.Value->AsNumber()); - } - } - } -} - -FNakamaMatchmakerUser::FNakamaMatchmakerUser() -{ - -} - -FNakamaMatchmakerMatched::FNakamaMatchmakerMatched(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("ticket"), Ticket); - JsonObject->TryGetStringField(TEXT("match_id"), MatchId); - JsonObject->TryGetStringField(TEXT("token"), Token); - - const TArray>* UsersJsonArray; - if (JsonObject->TryGetArrayField(TEXT("users"), UsersJsonArray)) - { - for (const TSharedPtr& UserJson : *UsersJsonArray) - { - if (TSharedPtr UserJsonObject = UserJson->AsObject()) - { - Users.Add(FNakamaMatchmakerUser(UserJsonObject)); - } - } - } - - const TSharedPtr* MeJsonObject; - if (JsonObject->TryGetObjectField(TEXT("self"), MeJsonObject)) - { - Me = FNakamaMatchmakerUser(*MeJsonObject); - } - } -} - -FNakamaMatchmakerMatched::FNakamaMatchmakerMatched() -{ - -} - -FNakamaMatchPresenceEvent::FNakamaMatchPresenceEvent(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("match_id"), MatchId); - - const TArray>* JoinsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("joins"), JoinsJsonArray)) - { - for (const TSharedPtr& JoinJson : *JoinsJsonArray) - { - if (TSharedPtr JoinJsonObject = JoinJson->AsObject()) - { - Joins.Add(FNakamaUserPresence(JoinJsonObject)); - } - } - } - - const TArray>* LeavesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("leaves"), LeavesJsonArray)) - { - for (const TSharedPtr& LeaveJson : *LeavesJsonArray) - { - if (TSharedPtr LeaveJsonObject = LeaveJson->AsObject()) - { - Leaves.Add(FNakamaUserPresence(LeaveJsonObject)); - } - } - } - } -} - -FNakamaMatchPresenceEvent::FNakamaMatchPresenceEvent() -{ - -} - -FNakamaMatchmakerTicket::FNakamaMatchmakerTicket(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - TSharedPtr MatchmakerTicketObject = JsonObject->GetObjectField(TEXT("matchmaker_ticket")); - if (MatchmakerTicketObject.IsValid()) - { - MatchmakerTicketObject->TryGetStringField(TEXT("ticket"), TicketId); - } - } -} - -FNakamaMatchmakerTicket::FNakamaMatchmakerTicket() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaNotification.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaNotification.cpp deleted file mode 100644 index bbda56576..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaNotification.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaNotification.h" -#include "NakamaUtils.h" - -FNakamaNotification::FNakamaNotification(const FString& JsonString) : FNakamaNotification(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaNotification::FNakamaNotification(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("subject"), Subject); - JsonObject->TryGetStringField(TEXT("content"), Content); - JsonObject->TryGetNumberField(TEXT("code"), Code); - JsonObject->TryGetStringField(TEXT("sender_id"), SenderId); - - FString CreateTimeString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreateTimeString)) - { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - - JsonObject->TryGetBoolField(TEXT("persistent"), Persistent); - } -} - -FNakamaNotification::FNakamaNotification() -{ - -} - -FNakamaNotificationList::FNakamaNotificationList(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* NotificationsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("notifications"), NotificationsJsonArray)) - { - for (const TSharedPtr& NotificationJson : *NotificationsJsonArray) - { - if (TSharedPtr NotificationJsonObject = NotificationJson->AsObject()) - { - Notifications.Add(FNakamaNotification(NotificationJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cacheable_cursor"), CacheableCursor); - } -} - -FNakamaNotificationList::FNakamaNotificationList() -{ - -} - \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaParty.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaParty.cpp deleted file mode 100644 index 0ca1d021a..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaParty.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaParty.h" -#include "NakamaUtils.h" - -FNakamaParty::FNakamaParty(const FString& JsonString) : FNakamaParty(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaParty::FNakamaParty(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - // Get the appropriate object based on whether "party" is present or not - TSharedPtr PartyObject = JsonObject->HasField(TEXT("party")) ? JsonObject->GetObjectField(TEXT("party")) : JsonObject; - - if (!PartyObject->TryGetStringField(TEXT("party_id"), PartyId)) - { - // TODO: Check which is the one actually sent by the server - PartyObject->TryGetStringField(TEXT("partyId"), PartyId); - } - PartyObject->TryGetBoolField(TEXT("open"), Open); - PartyObject->TryGetBoolField(TEXT("hidden"), Hidden); - if (!PartyObject->TryGetNumberField(TEXT("max_size"), MaxSize)) - { - // TODO: Check which is the one actually sent by the server - PartyObject->TryGetNumberField(TEXT("maxSize"), MaxSize); - } - PartyObject->TryGetStringField(TEXT("label"), Label); - - FString SelfJsonString; - const TSharedPtr* SelfJsonObject; - if (PartyObject->TryGetObjectField(TEXT("self"), SelfJsonObject)) - { - Me = FNakamaUserPresence(*SelfJsonObject); - } - - FString LeaderJsonString; - const TSharedPtr* LeaderJsonObject; - if (PartyObject->TryGetObjectField(TEXT("leader"), LeaderJsonObject)) - { - Leader = FNakamaUserPresence(*LeaderJsonObject); - } - - const TArray>* PresencesJsonArray; - if (PartyObject->TryGetArrayField(TEXT("presences"), PresencesJsonArray)) - { - for (const TSharedPtr& PresenceJson : *PresencesJsonArray) - { - if (TSharedPtr PresenceObject = PresenceJson->AsObject()) - { - Presences.Add(FNakamaUserPresence(PresenceObject)); - } - } - } - } - -} - -FNakamaParty::FNakamaParty(): Open(false), Hidden(true), MaxSize(0) -{ -} - -FNakamaPartyJoinRequest::FNakamaPartyJoinRequest(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - // Get the appropriate object based on whether "party_join_request" is present or not. - TSharedPtr PartyJoinRequestJsonObject = JsonObject->HasField(TEXT("party_join_request")) ? JsonObject->GetObjectField(TEXT("party_join_request")) : JsonObject; - - PartyJoinRequestJsonObject->TryGetStringField(TEXT("party_id"), PartyId); - - const TArray>* PresencesJsonArray; - if (PartyJoinRequestJsonObject->TryGetArrayField(TEXT("presences"), PresencesJsonArray)) - { - for (const TSharedPtr& PresenceJson : *PresencesJsonArray) - { - if (TSharedPtr PresenceObject = PresenceJson->AsObject()) - { - Presences.Add(FNakamaUserPresence(PresenceObject)); - } - } - } - } -} - -FNakamaPartyJoinRequest::FNakamaPartyJoinRequest() -{ - -} - -FNakamaPartyMatchmakerTicket::FNakamaPartyMatchmakerTicket(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - return; - } - - TSharedPtr PartyTicketObject = JsonObject->GetObjectField(TEXT("party_matchmaker_ticket")); - if (!PartyTicketObject.IsValid()) - { - return; - } - - PartyTicketObject->TryGetStringField(TEXT("ticket"), Ticket); - PartyTicketObject->TryGetStringField(TEXT("party_id"), PartyId); -} - -FNakamaPartyMatchmakerTicket::FNakamaPartyMatchmakerTicket() -{ - -} - - -FNakamaPartyClose::FNakamaPartyClose(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - Id = JsonObject->GetStringField(TEXT("id")); - } -} - -FNakamaPartyClose::FNakamaPartyClose() -{ - -} - -FNakamaPartyData::FNakamaPartyData(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("party_id"), PartyId); - - const TSharedPtr* PresenceJsonObject; - if (JsonObject->TryGetObjectField(TEXT("presence"), PresenceJsonObject)) - { - Presence = FNakamaUserPresence(*PresenceJsonObject); - } - else - { - Presence = FNakamaUserPresence(); - } - - JsonObject->TryGetNumberField(TEXT("op_code"), OpCode); - - if(JsonObject->HasField(TEXT("data"))) - { - FBase64::Decode(JsonObject->GetStringField(TEXT("data")), Data); - } - } -} - -FNakamaPartyData::FNakamaPartyData(): OpCode(0) -{ -} - -FNakamaPartyLeader::FNakamaPartyLeader(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("party_id"), PartyId); - - const TSharedPtr* PresenceJsonObject; - if (JsonObject->TryGetObjectField(TEXT("presence"), PresenceJsonObject)) - { - Presence = FNakamaUserPresence(*PresenceJsonObject); - } - else - { - Presence = FNakamaUserPresence(); - } - } -} - -FNakamaPartyLeader::FNakamaPartyLeader() -{ - -} - -FNakamaPartyPresenceEvent::FNakamaPartyPresenceEvent(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("party_id"), PartyId); - - const TArray>* JoinsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("joins"), JoinsJsonArray)) - { - for (const TSharedPtr& PresenceJson : *JoinsJsonArray) - { - if (TSharedPtr PresenceObject = PresenceJson->AsObject()) - { - Joins.Add(FNakamaUserPresence(PresenceObject)); - } - } - } - - const TArray>* LeavesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("leaves"), LeavesJsonArray)) - { - for (const TSharedPtr& PresenceJson : *LeavesJsonArray) - { - if (TSharedPtr PresenceObject = PresenceJson->AsObject()) - { - Leaves.Add(FNakamaUserPresence(PresenceObject)); - } - } - } - } -} - -FNakamaPartyPresenceEvent::FNakamaPartyPresenceEvent() -{ - -} - - -FNakamaPartyList::FNakamaPartyList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* PartiesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("parties"), PartiesJsonArray)) - { - for (const TSharedPtr& PartyJsonValue : *PartiesJsonArray) - { - if (TSharedPtr PartyJsonObject = PartyJsonValue->AsObject()) - { - FNakamaParty Party(PartyJsonObject); - Parties.Add(Party); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - -FNakamaPartyList::FNakamaPartyList() -{ -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaPresence.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaPresence.cpp deleted file mode 100644 index 8b71b21d0..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaPresence.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaPresence.h" -#include "NakamaUtils.h" - -FNakamaUserPresence::FNakamaUserPresence(const FString& JsonString) : FNakamaUserPresence(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaUserPresence::FNakamaUserPresence(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("user_id"), UserID); - JsonObject->TryGetStringField(TEXT("session_id"), SessionID); - JsonObject->TryGetStringField(TEXT("username"), Username); - JsonObject->TryGetBoolField(TEXT("persistence"), Persistence); - JsonObject->TryGetStringField(TEXT("status"), Status); - } -} - - -FNakamaUserPresence::FNakamaUserPresence() -{ - -} \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaRPC.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaRPC.cpp deleted file mode 100644 index e1b4dfa3c..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaRPC.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaRPC.h" - -#include "NakamaUtils.h" - -FNakamaRPC::FNakamaRPC(const FString& JsonString) -{ - TSharedPtr RootJsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, RootJsonObject) && RootJsonObject.IsValid()) - { - // Check if the "rpc" object exists - if (RootJsonObject->HasField(TEXT("rpc"))) - { - TSharedPtr JsonObject = RootJsonObject->GetObjectField(TEXT("rpc")); - - // Now extract the fields from the "rpc" object - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("payload"), Payload); - JsonObject->TryGetStringField(TEXT("http_key"), HttpKey); - } - } - else - { - RootJsonObject->TryGetStringField(TEXT("id"), Id); - RootJsonObject->TryGetStringField(TEXT("payload"), Payload); - RootJsonObject->TryGetStringField(TEXT("http_key"), HttpKey); - } - } -} - -FNakamaRPC::FNakamaRPC(FString&& JsonString) -{ - TSharedPtr RootJsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(MoveTemp(JsonString)); - - if (FJsonSerializer::Deserialize(JsonReader, RootJsonObject) && RootJsonObject.IsValid()) - { - // Check if the "rpc" object exists - if (RootJsonObject->HasField(TEXT("rpc"))) - { - TSharedPtr JsonObject = RootJsonObject->GetObjectField(TEXT("rpc")); - - // Now extract the fields from the "rpc" object - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("payload"), Payload); - JsonObject->TryGetStringField(TEXT("http_key"), HttpKey); - } - } - else - { - RootJsonObject->TryGetStringField(TEXT("id"), Id); - RootJsonObject->TryGetStringField(TEXT("payload"), Payload); - RootJsonObject->TryGetStringField(TEXT("http_key"), HttpKey); - } - } -} - -FNakamaRPC::FNakamaRPC() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeClient.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeClient.cpp deleted file mode 100644 index 45b058f10..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeClient.cpp +++ /dev/null @@ -1,3124 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaRealtimeClient.h" - -#include "NakamaUtils.h" -#include "NakamaChannelTypes.h" -#include "NakamaRtError.h" -#include "NakamaMatch.h" -#include "NakamaMatchTypes.h" -#include "NakamaNotification.h" -#include "NakamaParty.h" -#include "NakamaStatus.h" -#include "NakamaStreams.h" -#include "WebSocketsModule.h" -#include "GenericPlatform/GenericPlatformHttp.h" - -void UNakamaRealtimeClient::Initialize(const FString& InHost, int32 InPort, bool InSSL) -{ - Host = InHost; - Port = InPort; - bUseSSL = InSSL; - - NAKAMA_LOG_INFO(FString::Printf(TEXT("Nakama Realtime Client Created on Port: %d"), InPort)); -} - -void UNakamaRealtimeClient::UseCustomWebsocket(TSharedPtr CustomWebSocket) -{ - // Check if a previous WebSocket object exists (safety check) - if (WebSocket) - { - CancelAllRequests(ENakamaRtErrorCode::DISCONNECTED); - CleanupWebSocket(); - } - - bIsCustomWebsocketSet = CustomWebSocket.IsValid(); - WebSocket = CustomWebSocket; -} - -void UNakamaRealtimeClient::Connect( - UNakamaSession* Session, - bool bCreateStatus, - const FOnRealtimeClientConnected& Success, - const FOnRealtimeClientConnectionError& ConnectionError) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, ConnectionError](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - ConnectionError.Broadcast(error); - }; - - Connect(Session, bCreateStatus, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::Connect( - UNakamaSession* Session, - bool bCreateStatus, - TFunction Success, - TFunction ConnectionError) -{ - // Start by checking session validity - if (!Session || !Session->IsValidLowLevel()) - { - NAKAMA_LOG_WARN(TEXT("Session is invalid. Aborting connection attempt.")); - - FNakamaRtError InvalidSessionError; - InvalidSessionError.Message = TEXT("Realtime Client Connect. Session is invalid."); - InvalidSessionError.Code = ENakamaRtErrorCode::BAD_INPUT; - - // Execute the provided error callback - if(ConnectionError) - { - ConnectionError(InvalidSessionError); - } - - // Broadcast Multicast Delegate Event - ConnectionErrorEvent.Broadcast(InvalidSessionError); - ConnectionErrorEventNative.Broadcast(InvalidSessionError); - - return; - } - - // Check if connetion is ongoing - // remove this block if you want to create a new socket connection while one is already active - if (ConnectionState != EConnectionState::Disconnected) - { - NAKAMA_LOG_WARN(TEXT("A connection process is currently active. Aborting new connection attempt.")); - - FNakamaRtError ExistingConnectionError; - ExistingConnectionError.Message = TEXT("A connection process is already active."); - ExistingConnectionError.Code = ENakamaRtErrorCode::CONNECT_ERROR; - - // Execute Lambda - if(ConnectionError) - { - ConnectionError(ExistingConnectionError); - } - - // Broadcast Multicast Delegate Event - ConnectionErrorEvent.Broadcast(ExistingConnectionError); - ConnectionErrorEventNative.Broadcast(ExistingConnectionError); - - return; - } - - ConnectionState = EConnectionState::Connecting; - - if(!FModuleManager::Get().IsModuleLoaded("WebSockets")) - { - FModuleManager::Get().LoadModule("WebSockets"); - } - - // Check if a previous WebSocket object exists (safety check) - if (WebSocket) - { - CancelAllRequests(ENakamaRtErrorCode::DISCONNECTED); - CleanupWebSocket(); - } - - if (!bIsCustomWebsocketSet) - { - FString Url; - - if (bUseSSL) - { - Url = TEXT("wss://"); - } - else - { - Url = TEXT("ws://"); - } - - const FString EncodedToken = FGenericPlatformHttp::UrlEncode(Session->GetAuthToken()); - Url += FString(Host + TEXT(":") + FString::FromInt(Port) + TEXT("/ws")); - Url += TEXT("?token=") + EncodedToken; - Url += TEXT("&status=") + FNakamaUtils::BoolToString(bCreateStatus); - - WebSocket = FWebSocketsModule::Get().CreateWebSocket(Url); - } - - WebSocket->OnConnected().AddLambda([this, Success]() - { - NAKAMA_LOG_INFO(TEXT("Realtime Client Connected")); - - ConnectionState = EConnectionState::Connected; - - // Handle callbacks - if(FNakamaUtils::IsRealtimeClientActive(this)) - { - // This function (Connect) - if(Success) - { - Success(); - } - - // Call Lambda - if(OnConnect) - { - OnConnect(); - } - - // Broadcast Event Multicast Delegate - ConnectedEvent.Broadcast(); - ConnectedEventNative.Broadcast(); - } - }); - - WebSocket->OnConnectionError().AddLambda([this, ConnectionError](const FString& Error) - { - NAKAMA_LOG_ERROR(FString::Printf(TEXT("Realtime Client Connection Error: %s"), *Error)); - // Call connection error callback if listener is set and OnConnectionError is bound - // Checking validity is important here - - ConnectionState = EConnectionState::Disconnected; - - FNakamaRtError ConnectionRtError; - ConnectionRtError.Message = Error; - ConnectionRtError.Code = ENakamaRtErrorCode::CONNECT_ERROR; - - // Handle Callbacks - if(FNakamaUtils::IsRealtimeClientActive(this)) - { - // This function (Connect) - if(ConnectionError) - { - ConnectionError(ConnectionRtError); - } - - // Call Lambda - if(OnConnectionError) - { - OnConnectionError(ConnectionRtError); - } - - // Broadcast Event Multicast Delegate - ConnectionErrorEvent.Broadcast(ConnectionRtError); - ConnectionErrorEventNative.Broadcast(ConnectionRtError); - } - }); - - WebSocket->OnClosed().AddLambda([this](int32 StatusCode, const FString& Reason, bool bWasClean) - { - NAKAMA_LOG_INFO(FString::Printf(TEXT("Realtime Client Connection closed with status code: %d, reason: %s, was clean: %d"), StatusCode, *Reason, bWasClean)); - - ConnectionState = EConnectionState::Disconnected; - - CancelAllRequests(ENakamaRtErrorCode::DISCONNECTED); - - // Call disconnect callback if OnDisconnect is bound and DisconnectedEvent is bound - // This validity check is important - if(IsValidLowLevel()) - { - FNakamaDisconnectInfo DisconnectInfo; - DisconnectInfo.Code = DisconnectInfo.ConvertIntToDisconnectCode(StatusCode); - DisconnectInfo.Reason = Reason; - - // Handle remote disconnect - if(!bLocalDisconnectInitiated) - { - DisconnectInfo.Remote = true; - } - else - { - DisconnectInfo.Remote = false; - bLocalDisconnectInitiated = false; // Reset for future use - } - - // Call Lambda - if(OnDisconnect) - { - OnDisconnect(DisconnectInfo); - } - - // Broadcast Event Multicast Delegate - DisconnectedEvent.Broadcast(DisconnectInfo); - DisconnectedEventNative.Broadcast(DisconnectInfo); - } - }); - - WebSocket->OnMessage().AddLambda([this](const FString& MessageString) - { - // Parse the message - HandleReceivedMessage(MessageString); - - // Update the last message timestamp - LastMessageTimestamp = FPlatformTime::Seconds(); - }); - - WebSocket->OnMessageSent().AddLambda([](const FString& MessageString) - { - // Only print message if not ping - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(MessageString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - if (!JsonObject->HasField(TEXT("ping"))) - { - //NAKAMA_LOG_INFO(TEXT("...")); - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Realtime Client: Sent Message: %s"), *MessageString)); - } - } - }); - - bIsActive = true; - - WebSocket->Connect(); -} - - -/** - * Listen on All Events - */ - -void UNakamaRealtimeClient::SetListenerAllCallbacks() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerAllCallbacks()' bind directly to the events instead")); -} - -/** - * Setup Specific Listeners - */ - -void UNakamaRealtimeClient::SetListenerConnectCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerConnectCallback()' bind directly to the 'ConnectedEvent' event instead")); -} - -void UNakamaRealtimeClient::SetListenerConnectionErrorCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerConnectionErrorCallback()' bind to the 'ConnectionErrorEvent' event instead")); -} - -void UNakamaRealtimeClient::SetListenerDisconnectCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerDisconnectCallback()' bind to the 'DisconnectedEvent' event instead")); -} - -void UNakamaRealtimeClient::SetListenerErrorCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerErrorCallback()' bind to the 'ErrorEvent' event instead")); -} - -void UNakamaRealtimeClient::SetListenerChannelMessageCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerChannelMessageCallback()' bind to the 'ChannelMessageReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerChannelPresenceCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerChannelPresenceCallback()' bind to the 'ChannelPresenceEventReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerMatchmakerMatchedCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerMatchmakerMatchedCallback()' bind to the 'MatchmakerMatchMatched' event instead")); -} - -void UNakamaRealtimeClient::SetListenerMatchDataCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerMatchDataCallback()' bind to the 'MatchDataCallback' event instead")); -} - -void UNakamaRealtimeClient::SetListenerMatchPresenceCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerMatchPresenceCallback()' bind to the 'MatchmakerPresenceCallback' event instead")); -} - -void UNakamaRealtimeClient::SetListenerNotificationsCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerNotificationsCallback()' bind to the 'NotificationReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyCallback()' bind to the 'PartyReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyCloseCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyCloseCallback()' bind to the 'PartyCloseReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyDataCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyDataCallback()' bind to the 'PartyDataReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyJoinRequestCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyJoinRequestCallback()' bind to the 'PartyJoinRequestReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyLeaderCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyLeaderCallback()' bind to the 'PartyLeaderReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyMatchmakerTicketCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyMatchmakerTicketCallback()' bind to the 'PartyMatchmakerTicketReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerPartyPresenceCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerPartyPresenceCallback()' bind to the 'PartyPresenceReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerStatusPresenceCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerStatusPresenceCallback()' bind to the 'PresenceStatusReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerStreamPresenceCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerStreamPresenceCallback()' bind to the 'StreamPresenceEventReceived' event instead")); -} - -void UNakamaRealtimeClient::SetListenerStreamDataCallback() -{ - NAKAMA_LOG_DEBUG(TEXT("You no longer need to call 'SetListenerStreamDataCallback()' bind to the 'StreamPresenceDataReceived' event instead")); -} - -void UNakamaRealtimeClient::Destroy() -{ - bIsActive = false; - ConditionalBeginDestroy(); -} - -void UNakamaRealtimeClient::BeginDestroy() -{ - bIsActive = false; - - // Clear the request contexts in a thread-safe manner. - CancelAllRequests(ENakamaRtErrorCode::DISCONNECTED); - - CleanupWebSocket(); - - Super::BeginDestroy(); -} - -void UNakamaRealtimeClient::Disconnect() -{ - if (ConnectionState != EConnectionState::Connected) - { - NAKAMA_LOG_WARN(TEXT("Not currently connected. Aborting disconnect attempt.")); - return; - } - - ConnectionState = EConnectionState::Disconnecting; - - if(!WebSocket.IsValid()) - { - return; - } - - bLocalDisconnectInitiated = true; - - // NOTE: We do NOT clear binding for 'OnClosed' because it will clean up the Socket Connection - WebSocket->OnConnectionError().Clear(); - WebSocket->OnRawMessage().Clear(); - WebSocket->OnConnected().Clear(); - WebSocket->OnMessage().Clear(); - WebSocket->OnMessageSent().Clear(); - WebSocket->Close(); - - // TODO: We could also set 'ConnectionState' to 'Disconnected' here instead of waiting for the 'OnClosed' callback - // if you want that behaviour move the code from 'OnClosed' to here - //ConnectionState = EConnectionState::Disconnected; - //CancelAllRequests(ENakamaRtErrorCode::DISCONNECTED); - // Then broadcast Disconnected Lambda/Multicast Delegate -} - -/** - * Messaging - */ - -void UNakamaRealtimeClient::SendMessage( - const FString& ChannelId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error) -{ - WriteChatMessage(ChannelId, Content, Success, Error); -} - -void UNakamaRealtimeClient::WriteChatMessage( - const FString& ChannelId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(ChannelMessageAck); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - WriteChatMessage(ChannelId, Content, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::SendDirectMessage( - const FString& UserID, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(ChannelMessageAck); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - WriteChatMessage(UserID, Content, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::UpdateChatMessage( - const FString& ChannelId, - const FString& MessageId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(ChannelMessageAck); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateChatMessage(ChannelId, MessageId, Content, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::RemoveChatMessage( - const FString& ChannelId, - const FString& MessageId, - FOnWriteChatMessage Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaChannelMessageAck& ChannelMessageAck) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(ChannelMessageAck); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - RemoveChatMessage(ChannelId, MessageId, successCallback, errorCallback); -} - - -/** - * Chat System - */ - -void UNakamaRealtimeClient::JoinChat( - const FString& ChatId, - ENakamaChannelType ChannelType, - bool Persistence, - bool Hidden, - FOnJoinChat Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaChannel& Channel) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Channel); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - JoinChat(ChatId, ChannelType, Persistence, Hidden, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::LeaveChat( - const FString& ChannelId, - FOnLeaveChat Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LeaveChat(ChannelId, successCallback, errorCallback); -} - -/** - * Matchmaking System - */ - -void UNakamaRealtimeClient::AddMatchmaker( - int32 MinCount, - int32 MaxCount, - const FString& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - int32 CountMultiple, - bool IgnoreCountMultiple, - FOnMatchmakerTicket Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaMatchmakerTicket& MatchmakerTicket) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(MatchmakerTicket.TicketId); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - auto OptMinCount = FNakamaUtils::CreateOptional(MinCount, 0); - auto OptMaxCount = FNakamaUtils::CreateOptional(MaxCount, 0); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCountMultiple = FNakamaUtils::CreateOptional(CountMultiple, 0); - - AddMatchmaker( - OptMinCount, - OptMaxCount, - OptQuery, - StringProperties, - NumericProperties, - OptCountMultiple, - successCallback, - errorCallback - ); -} - -void UNakamaRealtimeClient::LeaveMatchmaker( - const FString& Ticket, - FOnRemovedMatchmakerTicket Success, - FOnRtError Error) -{ - RemoveMatchmaker(Ticket, Success, Error); -} - -void UNakamaRealtimeClient::RemoveMatchmaker( - const FString& Ticket, - FOnRemovedMatchmakerTicket Success, - FOnRtError Error) -{ - auto successCallback = [this, Success, Ticket]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Ticket); // Deviation from the C++ SDK by returning the ticket - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - RemoveMatchmaker(Ticket, successCallback, errorCallback); -} - -/** - * Status System - */ - -void UNakamaRealtimeClient::UpdateStatus( - const FString& StatusMessage, - FOnSetStatus Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateStatus(StatusMessage, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::SetAppearOffline( - FOnSetStatus Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateStatus("", successCallback, errorCallback); // "Invisible" Status -} - -void UNakamaRealtimeClient::FollowUsers( - const TArray& UserIds, - FOnFollowUsers Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaStatus& Status) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Status); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - FollowUsers(UserIds, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::UnFollowUsers( - const TArray& UserIds, - FOnUnFollowUsers Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UnfollowUsers(UserIds, successCallback, errorCallback); -} - -/** - * Match System - */ - -void UNakamaRealtimeClient::CreateMatch( - FOnCreateMatch Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaMatch& Match) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Match); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - CreateMatch(successCallback, errorCallback); -} - -void UNakamaRealtimeClient::JoinMatch( - const FString& MatchId, - const TMap& MetaData, - FOnCreateMatch Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaMatch& Match) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Match); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - JoinMatch(MatchId, MetaData, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::JoinMatchByToken( - const FString& Token, - FOnCreateMatch Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaMatch& Match) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Match); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - JoinMatchByToken(Token, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::SendMatchData( - const FString& MatchId, - int64 OpCode, - const FString& Data, - const TArray& Presences) -{ - // Setup the json object - const TSharedPtr MatchDataSend = MakeShareable(new FJsonObject()); - MatchDataSend->SetStringField(TEXT("match_id"), MatchId); - MatchDataSend->SetNumberField(TEXT("op_code"), OpCode); - - FString EncodeData = FNakamaUtils::Base64Encode(Data); - MatchDataSend->SetStringField(TEXT("data"), EncodeData); - - if (Presences.Num() > 0) - { - TArray> PresencesJsonArray; - for (const FNakamaUserPresence& Presence : Presences) - { - if (Presence.UserID.IsEmpty()) - { - NAKAMA_LOG_WARN(TEXT("Please set 'UserID' for user presence")); - continue; - } - - if (Presence.SessionID.IsEmpty()) - { - NAKAMA_LOG_WARN(TEXT("Please set 'SessionID' for user presence")); - continue; - } - - TSharedPtr PresenceJson = MakeShared(); - PresenceJson->SetStringField(TEXT("user_id"), Presence.UserID); - PresenceJson->SetStringField(TEXT("session_id"), Presence.SessionID); - - if (!Presence.Username.IsEmpty()) - { - PresenceJson->SetStringField(TEXT("username"), Presence.Username); - } - - PresencesJsonArray.Add(MakeShared(PresenceJson)); - } - - MatchDataSend->SetArrayField(TEXT("presences"), PresencesJsonArray); - } - - // This does not have callbacks - SendMessageWithEnvelope(TEXT("match_data_send"), MatchDataSend, {}, {}); -} - -void UNakamaRealtimeClient::LeaveMatch( - const FString& MatchId, - FOnLeaveMatch Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LeaveMatch(MatchId, successCallback, errorCallback); -} - -/** - * Party System - */ - - -void UNakamaRealtimeClient::CreateParty( - bool Open, - int32 MaxSize, - FOnCreateParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaParty& Party) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Party); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - CreateParty(Open, MaxSize, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::JoinParty( - const FString& PartyId, - FOnJoinParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success, PartyId]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(PartyId); // Deviation from C++ SDK by passing PartyId - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - JoinParty(PartyId, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::LeaveParty( - const FString& PartyId, - FOnLeaveParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - LeaveParty(PartyId, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::ListPartyJoinRequests( - const FString& PartyId, - FOnListPartyJoinRequests Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaPartyJoinRequest& PartyJoinRequest) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(PartyJoinRequest); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ListPartyJoinRequests(PartyId, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::PromotePartyMember( - const FString& PartyId, - const FNakamaUserPresence& PartyMember, - FOnPromotePartyMember Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - PromotePartyMember(PartyId, PartyMember, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::RemoveMatchMakerParty( - const FString& PartyId, - const FString& Ticket, - FOnRemoveMatchmakerParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success, Ticket]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(Ticket); // Deviation from C++ SDK by passing Ticket - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - RemoveMatchmakerParty(PartyId, Ticket, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::RemovePartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - FOnRemovePartyMember Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - RemovePartyMember(PartyId, Presence, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::AcceptPartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - FOnAcceptPartyMember Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AcceptPartyMember(PartyId, Presence, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::AddMatchmakerParty( - const FString& PartyId, - const FString& Query, - int32 MinCount, - int32 MaxCount, - const TMap& StringProperties, - const TMap& NumericProperties, - int32 CountMultiple, - bool IgnoreCountMultiple, - FOnAddMatchmakerParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaPartyMatchmakerTicket& MatchmakerTicket) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(MatchmakerTicket); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - auto OptMinCount = FNakamaUtils::CreateOptional(MinCount, 0); - auto OptMaxCount = FNakamaUtils::CreateOptional(MaxCount, 0); - const auto OptQuery = FNakamaUtils::CreateOptional(Query, FString()); - const auto OptCountMultiple = FNakamaUtils::CreateOptional(CountMultiple, 0); - - AddMatchmakerParty( - PartyId, - OptMinCount, - OptMaxCount, - OptQuery, - StringProperties, - NumericProperties, - OptCountMultiple, - successCallback, - errorCallback - ); -} - -void UNakamaRealtimeClient::CloseParty( - const FString& PartyId, - FOnCloseParty Success, - FOnRtError Error) -{ - auto successCallback = [this, Success]() - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - CloseParty(PartyId, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::RPC(const FString& FunctionId, const FString& Payload, FOnRtRPC Success, FOnRtError Error) -{ - auto successCallback = [this, Success](const FNakamaRPC& rpc) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Success.Broadcast(rpc); - }; - - auto errorCallback = [this, Error](const FNakamaRtError& error) - { - if(!FNakamaUtils::IsRealtimeClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - RPC(FunctionId, Payload, successCallback, errorCallback); -} - -void UNakamaRealtimeClient::JoinChat( - const FString& Target, - ENakamaChannelType ChannelType, - TOptional Persistence, - TOptional Hidden, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr ChannelJoin = MakeShareable(new FJsonObject()); - ChannelJoin->SetStringField(TEXT("target"), Target); - ChannelJoin->SetNumberField(TEXT("type"), static_cast(ChannelType)); - if (Persistence.IsSet()) - { - ChannelJoin->SetBoolField(TEXT("persistence"), Persistence.GetValue()); - } - if (Hidden.IsSet()) - { - ChannelJoin->SetBoolField(TEXT("hidden"), Hidden.GetValue()); - } - - SendMessageWithEnvelope(TEXT("channel_join"), ChannelJoin, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaChannel Channel = FNakamaChannel(Envelope.Payload); - SuccessCallback(Channel); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); - -} - -void UNakamaRealtimeClient::LeaveChat( - const FString& ChannelId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr ChannelLeave = MakeShareable(new FJsonObject()); - ChannelLeave->SetStringField(TEXT("channel_id"), ChannelId); - - SendMessageWithEnvelope(TEXT("channel_leave"), ChannelLeave, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::WriteChatMessage( - const FString& ChannelId, - const FString& Content, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr ChannelMessageSend = MakeShareable(new FJsonObject()); - ChannelMessageSend->SetStringField(TEXT("channel_id"), ChannelId); - ChannelMessageSend->SetStringField(TEXT("content"), Content); - - SendMessageWithEnvelope(TEXT("channel_message_send"), ChannelMessageSend, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaChannelMessageAck ChannelMessageAck = FNakamaChannelMessageAck(Envelope.Payload); - SuccessCallback(ChannelMessageAck); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::UpdateChatMessage( - const FString& ChannelId, - const FString& MessageId, - const FString& Content, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr ChannelMessageUpdate = MakeShareable(new FJsonObject()); - ChannelMessageUpdate->SetStringField(TEXT("channel_id"), ChannelId); - ChannelMessageUpdate->SetStringField(TEXT("message_id"), MessageId); - ChannelMessageUpdate->SetStringField(TEXT("content"), Content); - - SendMessageWithEnvelope(TEXT("channel_message_update"), ChannelMessageUpdate, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaChannelMessageAck ChannelMessageAck = FNakamaChannelMessageAck(Envelope.Payload); - SuccessCallback(ChannelMessageAck); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::RemoveChatMessage(const FString& ChannelId, - const FString& MessageId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr ChannelMessageRemove = MakeShareable(new FJsonObject()); - ChannelMessageRemove->SetStringField(TEXT("channel_id"), ChannelId); - ChannelMessageRemove->SetStringField(TEXT("message_id"), MessageId); - - SendMessageWithEnvelope(TEXT("channel_message_remove"), ChannelMessageRemove, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaChannelMessageAck ChannelMessageAck = FNakamaChannelMessageAck(Envelope.Payload); - SuccessCallback(ChannelMessageAck); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::CreateMatch( - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - SendMessageWithEnvelope(TEXT("match_create"), {}, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaMatch Match = FNakamaMatch(Envelope.Payload); - SuccessCallback(Match); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::JoinMatch( - const FString& MatchId, - const TMap& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr MatchJoin = MakeShareable(new FJsonObject()); - MatchJoin->SetStringField(TEXT("match_id"), MatchId); - - const TSharedPtr MetadataJson = MakeShareable(new FJsonObject()); - for (auto& Entry : Metadata) - { - MetadataJson->SetStringField(Entry.Key, Entry.Value); - } - - MatchJoin->SetObjectField(TEXT("metadata"), MetadataJson); - - SendMessageWithEnvelope(TEXT("match_join"), MatchJoin, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaMatch Match = FNakamaMatch(Envelope.Payload); - SuccessCallback(Match); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::JoinMatchByToken( - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr MatchJoin = MakeShareable(new FJsonObject()); - MatchJoin->SetStringField(TEXT("token"), Token); - - SendMessageWithEnvelope(TEXT("match_join"), MatchJoin, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaMatch Match = FNakamaMatch(Envelope.Payload); - SuccessCallback(Match); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::LeaveMatch( - const FString& MatchId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr MatchLeave = MakeShareable(new FJsonObject()); - MatchLeave->SetStringField(TEXT("match_id"), MatchId); - - SendMessageWithEnvelope(TEXT("match_leave"), MatchLeave, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::AddMatchmaker( - TOptional& MinCount, - TOptional& MaxCount, - const TOptional& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - const TOptional& CountMultiple, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr MatchmakerAdd = MakeShareable(new FJsonObject()); - if (MinCount.IsSet()) - { - MatchmakerAdd->SetNumberField(TEXT("min_count"), MinCount.GetValue()); - } - if (MaxCount.IsSet()) - { - MatchmakerAdd->SetNumberField(TEXT("max_count"), MaxCount.GetValue()); - } - if (Query.IsSet()) - { - MatchmakerAdd->SetStringField(TEXT("query"), Query.GetValue()); - } - if (CountMultiple.IsSet()) - { - MatchmakerAdd->SetNumberField(TEXT("count_multiple"), CountMultiple.GetValue()); - } - - if (StringProperties.Num() > 0) - { - const TSharedPtr StringPropertiesJson = MakeShareable(new FJsonObject()); - for (auto& Entry : StringProperties) - { - if(!Entry.Key.IsEmpty() && !Entry.Value.IsEmpty()) - { - StringPropertiesJson->SetStringField(Entry.Key, Entry.Value); - } - } - MatchmakerAdd->SetObjectField(TEXT("string_properties"), StringPropertiesJson); - } - - if (NumericProperties.Num() > 0) - { - const TSharedPtr NumericPropertiesJson = MakeShareable(new FJsonObject()); - for (auto& Entry : NumericProperties) - { - if(!Entry.Key.IsEmpty()) - { - NumericPropertiesJson->SetNumberField(Entry.Key, Entry.Value); - } - } - MatchmakerAdd->SetObjectField(TEXT("numeric_properties"), NumericPropertiesJson); - } - - SendMessageWithEnvelope(TEXT("matchmaker_add"), MatchmakerAdd, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaMatchmakerTicket MatchmakerTicket = FNakamaMatchmakerTicket(Envelope.Payload); - SuccessCallback(MatchmakerTicket); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::RemoveMatchmaker( - const FString& Ticket, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr MatchmakerRemove = MakeShareable(new FJsonObject()); - MatchmakerRemove->SetStringField(TEXT("ticket"), Ticket); - - SendMessageWithEnvelope(TEXT("matchmaker_remove"), MatchmakerRemove, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::FollowUsers( - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr StatusFollowUsers = MakeShared(); - TArray> UserIdsJsonArray; - - for (const FString& UserId : UserIds) - { - UserIdsJsonArray.Add(MakeShared(UserId)); - } - - StatusFollowUsers->SetArrayField(TEXT("user_ids"), UserIdsJsonArray); - - SendMessageWithEnvelope(TEXT("status_follow"), StatusFollowUsers, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaStatus Status = FNakamaStatus(Envelope.Payload); - SuccessCallback(Status); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::UnfollowUsers( - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr StatusUnfollowUsers = MakeShared(); - TArray> UserIdsJsonArray; - - for (const FString& UserId : UserIds) - { - UserIdsJsonArray.Add(MakeShared(UserId)); - } - - StatusUnfollowUsers->SetArrayField(TEXT("user_ids"), UserIdsJsonArray); - - SendMessageWithEnvelope(TEXT("status_unfollow"), StatusUnfollowUsers, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::UpdateStatus( - const FString& Status, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr StatusUpdate = MakeShared(); - StatusUpdate->SetStringField(TEXT("status"), Status); - - SendMessageWithEnvelope(TEXT("status_update"), StatusUpdate, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::RPC( - const FString& Id, - const TOptional& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr RpcSend = MakeShared(); - RpcSend->SetStringField(TEXT("id"), Id); - - if (Payload.IsSet()) - { - RpcSend->SetStringField(TEXT("payload"), Payload.GetValue()); - } - - SendMessageWithEnvelope(TEXT("rpc"), RpcSend, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaRPC Rpc = FNakamaRPC(Envelope.Payload); - SuccessCallback(Rpc); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::AcceptPartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyAccept = MakeShared(); - PartyAccept->SetStringField(TEXT("party_id"), PartyId); - - // Create the presence JSON object - const TSharedPtr PresenceJson = MakeShared(); - PresenceJson->SetStringField(TEXT("user_id"), Presence.UserID); - PresenceJson->SetBoolField(TEXT("persistence"), Presence.Persistence); - PresenceJson->SetStringField(TEXT("session_id"), Presence.SessionID); - PresenceJson->SetStringField(TEXT("username"), Presence.Username); - - // Set the presence JSON object in the PartyAccept JSON object - PartyAccept->SetObjectField(TEXT("presence"), PresenceJson); - - SendMessageWithEnvelope(TEXT("party_accept"), PartyAccept, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::AddMatchmakerParty( - const FString& PartyId, - TOptional& MinCount, - TOptional& MaxCount, - const TOptional& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - const TOptional& CountMultiple, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyMatchmakerAdd = MakeShared(); - PartyMatchmakerAdd->SetStringField(TEXT("party_id"), PartyId); - - if (MinCount.IsSet()) - { - PartyMatchmakerAdd->SetNumberField(TEXT("min_count"), MinCount.GetValue()); - } - - if (MaxCount.IsSet()) - { - PartyMatchmakerAdd->SetNumberField(TEXT("max_count"), MaxCount.GetValue()); - } - - if (Query.IsSet()) - { - PartyMatchmakerAdd->SetStringField(TEXT("query"), Query.GetValue()); - } - - if (StringProperties.Num() > 0) - { - const TSharedPtr StringPropertiesJson = MakeShared(); - for (const auto& StringProperty : StringProperties) - { - StringPropertiesJson->SetStringField(StringProperty.Key, StringProperty.Value); - } - - PartyMatchmakerAdd->SetObjectField(TEXT("string_properties"), StringPropertiesJson); - } - - if (NumericProperties.Num() > 0) - { - const TSharedPtr NumericPropertiesJson = MakeShared(); - for (const auto& NumericProperty : NumericProperties) - { - NumericPropertiesJson->SetNumberField(NumericProperty.Key, NumericProperty.Value); - } - - PartyMatchmakerAdd->SetObjectField(TEXT("numeric_properties"), NumericPropertiesJson); - } - - if (CountMultiple.IsSet()) - { - PartyMatchmakerAdd->SetNumberField(TEXT("count_multiple"), CountMultiple.GetValue()); - } - - SendMessageWithEnvelope(TEXT("party_matchmaker_add"), PartyMatchmakerAdd, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaPartyMatchmakerTicket MatchmakerTicket = FNakamaPartyMatchmakerTicket(Envelope.Payload); - SuccessCallback(MatchmakerTicket); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::CloseParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyClose = MakeShared(); - PartyClose->SetStringField(TEXT("party_id"), PartyId); - - SendMessageWithEnvelope(TEXT("party_close"), PartyClose, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::CreateParty( - bool bOpen, - int32 MaxSize, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyCreate = MakeShared(); - PartyCreate->SetBoolField(TEXT("open"), bOpen); - PartyCreate->SetNumberField(TEXT("max_size"), MaxSize); - - SendMessageWithEnvelope(TEXT("party_create"), PartyCreate, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaParty Party = FNakamaParty(Envelope.Payload); - SuccessCallback(Party); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::JoinParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyJoin = MakeShared(); - PartyJoin->SetStringField(TEXT("party_id"), PartyId); - - SendMessageWithEnvelope(TEXT("party_join"), PartyJoin, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::LeaveParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyLeave = MakeShared(); - PartyLeave->SetStringField(TEXT("party_id"), PartyId); - - SendMessageWithEnvelope(TEXT("party_leave"), PartyLeave, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::ListPartyJoinRequests( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyJoinRequestList = MakeShared(); - PartyJoinRequestList->SetStringField(TEXT("party_id"), PartyId); - - SendMessageWithEnvelope(TEXT("party_join_request_list"), PartyJoinRequestList, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - FNakamaPartyJoinRequest PartyJoinRequest = FNakamaPartyJoinRequest(Envelope.Payload); - SuccessCallback(PartyJoinRequest); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::PromotePartyMember( - const FString& PartyId, - const FNakamaUserPresence& PartyMember, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyPromote = MakeShared(); - PartyPromote->SetStringField(TEXT("party_id"), PartyId); - - // Create the presence JSON object - const TSharedPtr PresenceJson = MakeShared(); - PresenceJson->SetStringField(TEXT("user_id"), PartyMember.UserID); - PresenceJson->SetBoolField(TEXT("persistence"), PartyMember.Persistence); - PresenceJson->SetStringField(TEXT("session_id"), PartyMember.SessionID); - PresenceJson->SetStringField(TEXT("username"), PartyMember.Username); - - // Set the presence JSON object in the PartyAccept JSON object - PartyPromote->SetObjectField(TEXT("presence"), PresenceJson); - - SendMessageWithEnvelope(TEXT("party_promote"), PartyPromote, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::RemoveMatchmakerParty( - const FString& PartyId, - const FString& Ticket, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyRemoveMatchmaker = MakeShared(); - PartyRemoveMatchmaker->SetStringField(TEXT("party_id"), PartyId); - PartyRemoveMatchmaker->SetStringField(TEXT("ticket"), Ticket); - - SendMessageWithEnvelope(TEXT("party_matchmaker_remove"), PartyRemoveMatchmaker, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::RemovePartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the json object - const TSharedPtr PartyRemoveMember = MakeShared(); - PartyRemoveMember->SetStringField(TEXT("party_id"), PartyId); - - // Create the presence JSON object - const TSharedPtr PresenceJson = MakeShared(); - PresenceJson->SetStringField(TEXT("user_id"), Presence.UserID); - PresenceJson->SetBoolField(TEXT("persistence"), Presence.Persistence); - PresenceJson->SetStringField(TEXT("session_id"), Presence.SessionID); - PresenceJson->SetStringField(TEXT("username"), Presence.Username); - - // Set the presence JSON object in the PartyAccept JSON object - PartyRemoveMember->SetObjectField(TEXT("presence"), PresenceJson); - - SendMessageWithEnvelope(TEXT("party_remove"), PartyRemoveMember, - [SuccessCallback](const FNakamaRealtimeEnvelope& Envelope) - { - if (SuccessCallback) - { - SuccessCallback(); - } - }, - [ErrorCallback](const FNakamaRtError& Error) - { - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - ); -} - -void UNakamaRealtimeClient::SendPartyData( - const FString& PartyId, - int64 OpCode, - const FString& Data) -{ - // Setup the json object - const TSharedPtr PartySendData = MakeShared(); - PartySendData->SetStringField(TEXT("party_id"), PartyId); - PartySendData->SetNumberField(TEXT("op_code"), OpCode); - PartySendData->SetStringField(TEXT("data"), Data); - - const FString EncodeData = FNakamaUtils::Base64Encode(Data); - PartySendData->SetStringField(TEXT("data"), EncodeData); - - // This does not have callbacks - SendMessageWithEnvelope(TEXT("party_data_send"), PartySendData, {}, {}); -} - - -bool UNakamaRealtimeClient::IsConnected() const -{ - if(!WebSocket) - { - return false; - } - - return WebSocket->IsConnected(); -} - -int32 UNakamaRealtimeClient::GetHeartbeatIntervalMs() const -{ - return HeartbeatIntervalMs; -} - -void UNakamaRealtimeClient::SetHeartbeatIntervalMs(int32 IntervalMs) -{ - HeartbeatIntervalMs = IntervalMs; -} - -TObjectPtr UNakamaRealtimeClient::CreateReqContext(FNakamaRealtimeEnvelope& envelope) -{ - FScopeLock Lock(&ReqContextsLock); - - if (ReqContexts.Num() == 0 && NextCid > 9) - { - // Reset just to be one digit - // We can reset because there are no pending requests - NextCid = 0; - } - - TObjectPtr ReqContext = NewObject(); - - int32_t Cid = 0; - bool Inserted = false; - - int32_t NumTries = 10; - while (!Inserted && --NumTries > 0) - { - Cid = NextCid++; - Inserted = (ReqContexts.Add(Cid, ReqContext) != nullptr); - if (!Inserted) - { - NAKAMA_LOG_ERROR(FString::Printf(TEXT("Creating request with already assigned CID=%d"), Cid)); - } - } - - envelope.CID = Cid; - ReqContext->CID = Cid; - - return ReqContext; -} - -void UNakamaRealtimeClient::SendMessageWithEnvelope(const FString& FieldName, - const TSharedPtr& ObjectField, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Check WebSocket before sending Data - if (!WebSocket || !WebSocket->IsConnected()) - { - FNakamaRtError Error; - Error.Message = TEXT("WebSocket is not valid or not connected."); - Error.Code = ENakamaRtErrorCode::TRANSPORT_ERROR; - NAKAMA_LOG_ERROR(Error.Message); - - if(ErrorCallback) - { - ErrorCallback(Error); - } - - return; - } - - // Create the Envelope with the object field - const TSharedPtr Envelope = MakeShareable(new FJsonObject()); - //Envelope->SetObjectField(FieldName, ObjectField); - Envelope->SetObjectField(FieldName, ObjectField != nullptr ? ObjectField : MakeShareable(new FJsonObject())); - - // Make the Envelope - FNakamaRealtimeEnvelope NakamaEnvelope; - - // Create Context from the Envelope - TObjectPtr ReqContext = CreateReqContext(NakamaEnvelope); - Envelope->SetStringField(TEXT("cid"), FString::FromInt(ReqContext->CID)); - - // Envelope is basically just holding a reference to the Payload in the SuccessCallback (makes it generic) - // It is being set in the HandleMessage function - // Finally: Call the SuccessCallback with the Envelope - ReqContext->SuccessCallback.BindLambda([this, SuccessCallback](const FNakamaRealtimeEnvelope& ChannelEnvelope) - { - if(SuccessCallback) - { - SuccessCallback(ChannelEnvelope); - } - }); - - // Made into FNakamaRtError inside the HandleMessage function - ReqContext->ErrorCallback.BindLambda([this, ErrorCallback](const FNakamaRtError& Error) - { - if(ErrorCallback) - { - ErrorCallback(Error); - } - }); - - // Set the payload - NakamaEnvelope.Payload = FNakamaUtils::EncodeJson(Envelope); - - // Send Message - const FString Message = NakamaEnvelope.Payload; - WebSocket->Send(Message); - - NAKAMA_LOG_INFO(FString::Printf(TEXT("Realtime Client - Request %s sent with CID: %d"), *FieldName, ReqContext->CID)); -} - -void UNakamaRealtimeClient::SendMessageWithEnvelopeMove(const FString& FieldName, - const TSharedPtr& ObjectField, TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Check WebSocket before sending Data - if (!WebSocket || !WebSocket->IsConnected()) - { - FNakamaRtError Error; - Error.Message = TEXT("WebSocket is not valid or not connected."); - Error.Code = ENakamaRtErrorCode::TRANSPORT_ERROR; - NAKAMA_LOG_ERROR(Error.Message); - - if(ErrorCallback) - { - ErrorCallback(Error); - } - - return; - } - - // Create the Envelope with the object field - const TSharedPtr Envelope = MakeShareable(new FJsonObject()); - //Envelope->SetObjectField(FieldName, ObjectField); - Envelope->SetObjectField(FieldName, ObjectField != nullptr ? ObjectField : MakeShareable(new FJsonObject())); - - // Make the Envelope - FNakamaRealtimeEnvelope NakamaEnvelope; - - // Create Context from the Envelope - TObjectPtr ReqContext = CreateReqContext(NakamaEnvelope); - Envelope->SetStringField(TEXT("cid"), FString::FromInt(ReqContext->CID)); - - // Envelope is basically just holding a reference to the Payload in the SuccessCallback (makes it generic) - // It is being set in the HandleMessage function - // Finally: Call the SuccessCallback with the Envelope - ReqContext->SuccessCallbackMove.BindLambda([this, SuccessCallback](FNakamaRealtimeEnvelope&& ChannelEnvelope) - { - if(SuccessCallback) - { - SuccessCallback(MoveTemp(ChannelEnvelope)); - } - }); - - // Made into FNakamaRtError inside the HandleMessage function - ReqContext->ErrorCallback.BindLambda([this, ErrorCallback](const FNakamaRtError& Error) - { - if(ErrorCallback) - { - ErrorCallback(Error); - } - }); - - // Set the payload - NakamaEnvelope.Payload = FNakamaUtils::EncodeJson(Envelope); - - // Send Message - const FString Message = NakamaEnvelope.Payload; - WebSocket->Send(Message); - - NAKAMA_LOG_INFO(FString::Printf(TEXT("Realtime Client - Request %s sent with CID: %d"), *FieldName, ReqContext->CID)); -} - -void UNakamaRealtimeClient::SendDataWithEnvelope(const FString& FieldName, const TSharedPtr& ObjectField) -{ - // Check WebSocket before sending Data - if (!WebSocket || !WebSocket->IsConnected()) - { - NAKAMA_LOG_ERROR(TEXT("WebSocket is not valid or not connected.")); - return; - } - - // Create the Envelope with the object field - const TSharedPtr Envelope = MakeShareable(new FJsonObject()); - //Envelope->SetObjectField(FieldName, ObjectField); - Envelope->SetObjectField(FieldName, ObjectField != nullptr ? ObjectField : MakeShareable(new FJsonObject())); - - // Make the Envelope - FNakamaRealtimeEnvelope NakamaEnvelope; - - // Create Context from the Envelope - const TObjectPtr ReqContext = CreateReqContext(NakamaEnvelope); - Envelope->SetStringField(TEXT("cid"), FString::FromInt(ReqContext->CID)); - - // Set the payload - FString JsonPayload = FNakamaUtils::EncodeJson(Envelope); - - const FString EncodeData = FBase64::Encode(JsonPayload); - NakamaEnvelope.Payload = EncodeData; - - // Send Message - const FString Message = NakamaEnvelope.Payload; - WebSocket->Send(Message); - - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("%s request sent with CID=%d"), *FieldName, ReqContext->CID)); -} - -void UNakamaRealtimeClient::CleanupWebSocket() -{ - if (!WebSocket.IsValid()) - { - return; - } - - // Indicate that the disconnect was initiated locally. - bLocalDisconnectInitiated = true; - - // If the WebSocket is still connected, close the connection. - if (WebSocket->IsConnected()) - { - WebSocket->Close(); - } - - // Clear all bound event delegates to ensure no callbacks are made after this point. - WebSocket->OnClosed().Clear(); - WebSocket->OnConnectionError().Clear(); - WebSocket->OnRawMessage().Clear(); - WebSocket->OnConnected().Clear(); - WebSocket->OnMessage().Clear(); - WebSocket->OnMessageSent().Clear(); - - // Reset the WebSocket pointer. - WebSocket.Reset(); -} - -void UNakamaRealtimeClient::SendPing() -{ - TSharedPtr PingRequest = MakeShareable(new FJsonObject()); - SendMessage(TEXT("ping"), PingRequest); -} - -void UNakamaRealtimeClient::Heartbeat() -{ - SendPing(); -} - -void UNakamaRealtimeClient::HandleReceivedMessage(const FString& Data) -{ - // Start by parsing the Json! - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Data); - if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - OnTransportError(FString::Printf(TEXT("Unable to parse message as JSON: %s"), *Data)); - return; - } - - // Only log if it is not a pong - if (!JsonObject->HasField(TEXT("pong"))) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Realtime Client - Received message: %s"), *Data)); - } - - FNakamaRtError Error; - if (JsonObject->HasField(TEXT("error"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("error")), JsonString)) - { - Error = FNakamaRtError(JsonString); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to serialize 'error' as JSON string."); - } - } - - // Check if CID is empty - FString CidStr; - if (!JsonObject->TryGetStringField(TEXT("cid"), CidStr)) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Received message with no CID: %s"), *Data)); - - // Handle Events here - if(FNakamaUtils::IsRealtimeClientActive(this)) - { - if (JsonObject->HasField(TEXT("error"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("error")), JsonString)) - { - // TODO: Look into if we need to get Error from JsonString (using ReturnedError) or use 'Error' from above - FNakamaRtError ReturnedError = FNakamaRtError(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnError) - { - OnError(ReturnedError); - } - - // Handle Multicast Delegate - ErrorEvent.Broadcast(ReturnedError); - ErrorEventNative.Broadcast(ReturnedError); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'error' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("channel_message"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("channel_message")), JsonString)) - { - FNakamaChannelMessage ChannelMessage = FNakamaChannelMessage(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnChannelMessage) - { - OnChannelMessage(ChannelMessage); - } - - // Handle Multicast Delegate - ChannelMessageReceived.Broadcast(ChannelMessage); - ChannelMessageReceivedNative.Broadcast(ChannelMessage); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'channel_message' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("channel_presence_event"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("channel_presence_event")), JsonString)) - { - FNakamaChannelPresenceEvent ChannelPresenceEvent = FNakamaChannelPresenceEvent(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnChannelPresenceEvent) - { - OnChannelPresenceEvent(ChannelPresenceEvent); - } - - // Handle Multicast Delegate - ChannelPresenceEventReceived.Broadcast(ChannelPresenceEvent); - ChannelPresenceEventReceivedNative.Broadcast(ChannelPresenceEvent); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'channel_presence_event' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("match_data"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("match_data")), JsonString)) - { - FNakamaMatchData MatchData = FNakamaMatchData(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnMatchData) - { - OnMatchData(MatchData); - } - - // Handle Multicast Delegate - MatchDataCallback.Broadcast(MatchData); - MatchDataCallbackNative.Broadcast(MatchData); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'match_data' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("match_presence_event"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("match_presence_event")), JsonString)) - { - FNakamaMatchPresenceEvent MatchPresenceEvent = FNakamaMatchPresenceEvent(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnMatchPresenceEvent) - { - OnMatchPresenceEvent(MatchPresenceEvent); - } - - // Handle Multicast Delegate - MatchmakerPresenceCallback.Broadcast(MatchPresenceEvent); - MatchmakerPresenceCallbackNative.Broadcast(MatchPresenceEvent); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'match_presence_event' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("matchmaker_matched"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("matchmaker_matched")), JsonString)) - { - FNakamaMatchmakerMatched MatchmakerMatched = FNakamaMatchmakerMatched(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnMatchmakerMatched) - { - OnMatchmakerMatched(MatchmakerMatched); - } - - // Handle Multicast Delegate - MatchmakerMatchMatched.Broadcast(MatchmakerMatched); - MatchmakerMatchMatchedNative.Broadcast(MatchmakerMatched); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'matchmaker_matched' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("notifications"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("notifications")), JsonString)) - { - FNakamaNotificationList NotificationList = FNakamaNotificationList(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnNotifications) - { - OnNotifications(NotificationList); - } - - // Handle Multicast Delegate - NotificationReceived.Broadcast(NotificationList); - NotificationReceivedNative.Broadcast(NotificationList); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'notifications' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("status_presence_event"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("status_presence_event")), JsonString)) - { - FNakamaStatusPresenceEvent StatusPresenceEvent = FNakamaStatusPresenceEvent(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnStatusPresenceEvent) - { - OnStatusPresenceEvent(StatusPresenceEvent); - } - - // Handle Multicast Delegate - PresenceStatusReceived.Broadcast(StatusPresenceEvent); - PresenceStatusReceivedNative.Broadcast(StatusPresenceEvent); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'status_presence_event' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("stream_data"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("stream_data")), JsonString)) - { - FNakamaStreamData StreamData = FNakamaStreamData(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnStreamData) - { - OnStreamData(StreamData); - } - - // Handle Multicast Delegate - StreamPresenceDataReceived.Broadcast(StreamData); - StreamPresenceDataReceivedNative.Broadcast(StreamData); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'stream_data' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("stream_presence_event"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("stream_presence_event")), JsonString)) - { - FNakamaStreamPresenceEvent StreamPresenceEvent = FNakamaStreamPresenceEvent(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnStreamPresenceEvent) - { - OnStreamPresenceEvent(StreamPresenceEvent); - } - - // Handle Multicast Delegate - StreamPresenceEventReceived.Broadcast(StreamPresenceEvent); - StreamPresenceEventReceivedNative.Broadcast(StreamPresenceEvent); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'stream_presence_event' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party")), JsonString)) - { - FNakamaParty Party = FNakamaParty(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnParty) - { - OnParty(Party); - } - - // Handle Multicast Delegate - PartyReceived.Broadcast(Party); - PartyReceivedNative.Broadcast(Party); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_close"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_close")), JsonString)) - { - FNakamaPartyClose PartyClose = FNakamaPartyClose(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyClose) - { - OnPartyClose(PartyClose); - } - - // Handle Multicast Delegate - PartyCloseReceived.Broadcast(PartyClose); - PartyCloseReceivedNative.Broadcast(PartyClose); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_close' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_data"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_data")), JsonString)) - { - FNakamaPartyData PartyData = FNakamaPartyData(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyData) - { - OnPartyData(PartyData); - } - - // Handle Multicast Delegate - PartyDataReceived.Broadcast(PartyData); - PartyDataReceivedNative.Broadcast(PartyData); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_data' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_join_request"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_join_request")), JsonString)) - { - FNakamaPartyJoinRequest PartyJoinRequest = FNakamaPartyJoinRequest(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyJoinRequest) - { - OnPartyJoinRequest(PartyJoinRequest); - } - - // Handle Multicast Delegate - PartyJoinRequestReceived.Broadcast(PartyJoinRequest); - PartyJoinRequestReceivedNative.Broadcast(PartyJoinRequest); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_join_request' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_leader"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_leader")), JsonString)) - { - FNakamaPartyLeader PartyLeader = FNakamaPartyLeader(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyLeader) - { - OnPartyLeader(PartyLeader); - } - - // Handle Multicast Delegate - PartyLeaderReceived.Broadcast(PartyLeader); - PartyLeaderReceivedNative.Broadcast(PartyLeader); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_leader' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_matchmaker_ticket"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_matchmaker_ticket")), JsonString)) - { - FNakamaPartyMatchmakerTicket PartyMatchmakerTicket = FNakamaPartyMatchmakerTicket(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyMatchmakerTicket) - { - OnPartyMatchmakerTicket(PartyMatchmakerTicket); - } - - // Handle Multicast Delegate - PartyMatchmakerTicketReceived.Broadcast(PartyMatchmakerTicket); - PartyMatchmakerTicketReceivedNative.Broadcast(PartyMatchmakerTicket); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_matchmaker_ticket' from JSON string."); - } - } - else if (JsonObject->HasField(TEXT("party_presence_event"))) - { - FString JsonString; - if (SerializeJsonObject(JsonObject->GetObjectField(TEXT("party_presence_event")), JsonString)) - { - FNakamaPartyPresenceEvent PartyPresenceEvent = FNakamaPartyPresenceEvent(MoveTemp(JsonString)); - - // Handle Lambda Callback - if(OnPartyPresenceEvent) - { - OnPartyPresenceEvent(PartyPresenceEvent); - } - - // Handle Multicast Delegate - PartyPresenceReceived.Broadcast(PartyPresenceEvent); - PartyPresenceReceivedNative.Broadcast(PartyPresenceEvent); - } - else - { - NAKAMA_LOG_ERROR("Realtime Client - Failed to deserialize 'party_presence_event' from JSON string."); - } - } - else - { - OnTransportError(TEXT("Realtime Client Listener - Unknown message received.")); - } - } - else - { - NAKAMA_LOG_ERROR(TEXT("Realtime Client - Realtime Client was not available to handle event message.")); - } - // End of Events! - } - else - { - // Handle Requests here - int32 Cid = FCString::Atoi(*CidStr); - - // NOTE: This log got a bit too verbose, enable it to see the CID - //NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Received message with CID: %d"), Cid)); - - FNakamaRealtimeSuccessCallback SuccessCallback; - FNakamaRealtimeSuccessCallbackMove SuccessCallbackMove; - FNakamaRealtimeErrorCallback ErrorCallback; - - bool bContextIsValid = false; - - { - FScopeLock Lock(&ReqContextsLock); - TObjectPtr ReqContext = ReqContexts.FindRef(Cid); - if (ReqContext) - { - bContextIsValid = true; - SuccessCallback = ReqContext->SuccessCallback; - SuccessCallbackMove = ReqContext->SuccessCallbackMove; - ErrorCallback = ReqContext->ErrorCallback; - ReqContexts.Remove(Cid); - } - else - { - NAKAMA_LOG_ERROR(FString::Printf(TEXT("Received response for unknown CID=%d"), Cid)); - return; - } - } - - if(bContextIsValid) - { - if (JsonObject->HasField(TEXT("error"))) - { - if (ErrorCallback.IsBound()) - { - ErrorCallback.Execute(Error); - } - else if (OnError || ErrorEvent.IsBound() || ErrorEventNative.IsBound()) // Checks if Error is bound (means it is handled) - { - // Lambda Callback - if(OnError) - { - OnError(Error); - } - - // Multicast Delegate - ErrorEvent.Broadcast(Error); - ErrorEventNative.Broadcast(Error); - } - else - { - // Error was not handled - NAKAMA_LOG_WARN(TEXT("Error not handled.")); - } - } - else - { - bool constRefBound = SuccessCallback.IsBound(); - bool moveBound = SuccessCallbackMove.IsBound(); - if (constRefBound || moveBound) - { - FNakamaRealtimeEnvelope Envelope; - Envelope.CID = Cid; - Envelope.Payload = Data; - if (constRefBound) - { - SuccessCallback.Execute(Envelope); - } - if (moveBound) - { - SuccessCallbackMove.Execute(MoveTemp(Envelope)); - } - } - } - } - else - { - OnTransportError(FString::Printf(TEXT("Request context not found CID: %d"), Cid)); - } - } -} - -void UNakamaRealtimeClient::SendMessage(const FString& FieldName, const TSharedPtr& Object) -{ - // Check WebSocket before sending Data - if (!WebSocket || !WebSocket->IsConnected()) - { - NAKAMA_LOG_ERROR(TEXT("WebSocket is not valid or not connected.")); - return; - } - - TSharedPtr Envelope = MakeShareable(new FJsonObject()); - Envelope->SetObjectField(FieldName, Object); - - // Make our own - FNakamaRealtimeEnvelope NakamaEnvelope; - - // Create Context - const TObjectPtr ReqContext = CreateReqContext(NakamaEnvelope); - Envelope->SetStringField(TEXT("cid"), FString::FromInt(ReqContext->CID)); - - // Set the payload - NakamaEnvelope.Payload = FNakamaUtils::EncodeJson(Envelope); - - // Send Message - FString Message = NakamaEnvelope.Payload; - WebSocket->Send(Message); -} - -void UNakamaRealtimeClient::Tick(float DeltaTime) -{ - AccumulatedDeltaTime += DeltaTime * 1000.0f; // Convert DeltaTime to milliseconds - if (AccumulatedDeltaTime >= HeartbeatIntervalMs) - { - Heartbeat(); - AccumulatedDeltaTime = 0.0f; - } -} - -bool UNakamaRealtimeClient::IsTickable() const -{ - // Tick only when the WebSocket is connected (?) - return WebSocket && WebSocket->IsConnected(); -} - -TStatId UNakamaRealtimeClient::GetStatId() const -{ - RETURN_QUICK_DECLARE_CYCLE_STAT(UNakamaRealtimeClient, STATGROUP_Tickables); -} - -bool UNakamaRealtimeClient::SerializeJsonObject(const TSharedPtr& JsonObject, FString& OutSerializedJson) -{ - if (!JsonObject.IsValid()) - { - return false; - } - - const TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&OutSerializedJson); - if (!FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter)) - { - JsonWriter->Close(); - return false; - } - - JsonWriter->Close(); - - return true; -} - -void UNakamaRealtimeClient::CancelAllRequests(const ENakamaRtErrorCode& ErrorCode) -{ - if(!IsValidLowLevel()) - { - return; - } - - FNakamaRtError Error; - Error.Code = ErrorCode; - Error.Message = TEXT(""); - - FScopeLock Lock(&ReqContextsLock); - - for (const auto& Pair : ReqContexts) - { - UNakamaRealtimeRequestContext* Context = Pair.Value; - - if (Context) - { - if(Context->ErrorCallback.IsBound()) - { - Context->ErrorCallback.Execute(Error); - } - } - } - - // Clear the Array - ReqContexts.Empty(); -} - -void UNakamaRealtimeClient::OnTransportError(const FString& Description) -{ - FNakamaRtError Error; - Error.Message = Description; - Error.Code = WebSocket->IsConnected() ? ENakamaRtErrorCode::TRANSPORT_ERROR : ENakamaRtErrorCode::CONNECT_ERROR; - - NAKAMA_LOG_ERROR(FString::Printf(TEXT("Realtime Client Transport Error (Code: %s): %s"), - WebSocket->IsConnected() ? TEXT("TRANSPORT_ERROR") : TEXT("CONNECT_ERROR"), *Description)); - - // Handle Callbacks - if(FNakamaUtils::IsRealtimeClientActive(this)) - { - // Lambda Callback - if(OnError) - { - OnError(Error); - } - - // Multicast Delegate - ErrorEvent.Broadcast(Error); - ErrorEventNative.Broadcast(Error); - } -} - diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeRequestContext.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeRequestContext.cpp deleted file mode 100644 index f363ba628..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaRealtimeRequestContext.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaRealtimeRequestContext.h" - diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaRtError.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaRtError.cpp deleted file mode 100644 index bd82e72f1..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaRtError.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaRtError.h" -#include "NakamaUtils.h" - -FNakamaRtError::FNakamaRtError(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("message"), Message); - - int32 CodeValue; - if (JsonObject->TryGetNumberField(TEXT("code"), CodeValue)) - { - Code = static_cast(CodeValue); - } - else - { - Code = ENakamaRtErrorCode::UNKNOWN_JSON; - } - - const TSharedPtr* ContextJsonObject; - if (JsonObject->TryGetObjectField(TEXT("context"), ContextJsonObject)) - { - for (auto& Pair : (*ContextJsonObject)->Values) - { - Context.Add(Pair.Key, Pair.Value->AsString()); - } - } - } -} - -ENakamaDisconnectCode FNakamaDisconnectInfo::ConvertIntToDisconnectCode(int32 Value) -{ - switch (Value) - { - case 1000: - return ENakamaDisconnectCode::NORMAL_CLOSURE; - case 1001: - return ENakamaDisconnectCode::GOING_AWAY; - case 1002: - return ENakamaDisconnectCode::PROTOCOL_ERROR; - case 1003: - return ENakamaDisconnectCode::UNSUPPORTED_DATA; - case 1005: - return ENakamaDisconnectCode::NO_STATUS_RCVD; - case 1006: - return ENakamaDisconnectCode::ABNORMAL_CLOSURE; - case 1007: - return ENakamaDisconnectCode::INVALID_FRAME_PAYLOAD_DATA; - case 1008: - return ENakamaDisconnectCode::POLICY_VIOLATION; - case 1009: - return ENakamaDisconnectCode::MESSAGE_TOO_BIG; - case 1010: - return ENakamaDisconnectCode::MANDATORY_EXT; - case 1011: - return ENakamaDisconnectCode::INTERNAL_SERVER_ERROR; - case 1015: - return ENakamaDisconnectCode::TLS_HANDSHAKE; - case 4000: - return ENakamaDisconnectCode::HEARTBEAT_FAILURE; - case 4001: - return ENakamaDisconnectCode::TRANSPORT_ERROR; - default: - return ENakamaDisconnectCode::NORMAL_CLOSURE; - } -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaSession.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaSession.cpp deleted file mode 100644 index 5acafdda5..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaSession.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaSession.h" - -#include "NakamaUtils.h" - - -UNakamaSession* UNakamaSession::SetupSession(const FString& AuthResponse) -{ - UNakamaSession* ResultSession = NewObject(); - TSharedPtr JsonObject = MakeShareable(new FJsonObject()); - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(AuthResponse); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - FString Token = JsonObject->GetStringField(TEXT("token")); - FString RefreshToken = JsonObject->GetStringField(TEXT("refresh_token")); - - ResultSession->SessionData.AuthToken = Token; - ResultSession->_AuthToken = Token; - ResultSession->SessionData.RefreshToken = RefreshToken; - ResultSession->_RefreshToken = RefreshToken; - - // Check if the "created" field is available and set IsCreated accordingly - bool IsSessionCreated; - if (JsonObject->TryGetBoolField(TEXT("created"), IsSessionCreated)) - { - ResultSession->SessionData.IsCreated = IsSessionCreated; - ResultSession->_IsCreated = IsSessionCreated; - } - - TSharedPtr PayloadJson; - if (ParseJwtPayload(Token, PayloadJson)) - { - FString UserId; - FString Username; - PayloadJson->TryGetStringField(TEXT("uid"), UserId); - PayloadJson->TryGetStringField(TEXT("usn"), Username); - - ResultSession->SessionData.Username = Username; - ResultSession->_Username = Username; - ResultSession->SessionData.UserId = UserId; - ResultSession->_UserId = UserId; - - TMap InVars; - if (PayloadJson->HasField(TEXT("vrs"))) - { - const TSharedPtr& VarsJson = PayloadJson->GetObjectField(TEXT("vrs")); - for (const auto& Entry : VarsJson->Values) - { - FString Key = Entry.Key; - FString Value = Entry.Value->AsString(); - ResultSession->SessionData.Variables.Add(Key, Value); - ResultSession->_Variables.Add(Key, Value); - } - } - - int64 Expires; - if (PayloadJson->TryGetNumberField(TEXT("exp"), Expires)) - { - FDateTime ExpireTime = FDateTime::FromUnixTimestamp(Expires); - ResultSession->SessionData.ExpireTime = ExpireTime; - ResultSession->_ExpireTime = ExpireTime; - ResultSession->SessionData.IsExpired = (FDateTime::UtcNow() >= ExpireTime); - ResultSession->_IsExpired = (FDateTime::UtcNow() >= ExpireTime); - } - - // Parse and check expiration time of the refresh_token - TSharedPtr RefreshPayloadJson; - if (ParseJwtPayload(RefreshToken, RefreshPayloadJson)) - { - int64 RefreshExpires; - if (RefreshPayloadJson->TryGetNumberField(TEXT("exp"), RefreshExpires)) - { - FDateTime RefreshExpireTime = FDateTime::FromUnixTimestamp(RefreshExpires); - ResultSession->SessionData.RefreshExpireTime = RefreshExpireTime; - ResultSession->_RefreshExpireTime = RefreshExpireTime; - ResultSession->SessionData.IsRefreshExpired = (FDateTime::UtcNow() >= RefreshExpireTime); - ResultSession->_IsRefreshExpired = (FDateTime::UtcNow() >= RefreshExpireTime); - } - } - - ResultSession->SessionData.CreateTime = FDateTime::UtcNow(); - ResultSession->_CreateTime = FDateTime::UtcNow(); - return ResultSession; - } - else - { - NAKAMA_LOG_ERROR(TEXT("Failed to parse JWT payload")); - } - } - else - { - NAKAMA_LOG_ERROR(TEXT("Failed to deserialize Session JSON object")); - } - return nullptr; -} - -const FString UNakamaSession::GetAuthToken() const -{ - return _AuthToken; -} - -const FString UNakamaSession::GetRefreshToken() const -{ - return _RefreshToken; -} - -bool UNakamaSession::IsCreated() const -{ - return _IsCreated; -} - -const FString UNakamaSession::GetUsername() const -{ - return _Username; -} - -const FString UNakamaSession::GetUserId() const -{ - return _UserId; -} - -const FDateTime UNakamaSession::GetCreateTime() const -{ - return _CreateTime; -} - -const FDateTime UNakamaSession::GetExpireTime() const -{ - return _ExpireTime; -} - -const FDateTime UNakamaSession::GetRefreshExpireTime() const -{ - return _RefreshExpireTime; -} - -bool UNakamaSession::IsExpired() const -{ - return FDateTime::UtcNow() >= _ExpireTime; -} - -bool UNakamaSession::IsExpiredTime(FDateTime Time) const -{ - return Time >= _ExpireTime; -} - -bool UNakamaSession::IsRefreshExpired() const -{ - return FDateTime::UtcNow() >= _RefreshExpireTime; -} - -bool UNakamaSession::IsRefreshExpiredTime(FDateTime Time) const -{ - return Time >= _RefreshExpireTime; -} - -TMap UNakamaSession::GetVariables() const -{ - return _Variables; -} - -FString UNakamaSession::GetVariable(FString Name) const -{ - return _Variables.FindRef(Name); -} - -UNakamaSession* UNakamaSession::RestoreSession(FString Token, FString RefreshToken) -{ - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - ContentJson->SetStringField(TEXT("token"), Token); - ContentJson->SetStringField(TEXT("refresh_token"), RefreshToken); - ContentJson->SetBoolField(TEXT("created"), false); - - // Convert the JSON object to a JSON string - FString JsonString; - TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&JsonString); - bool bSerializationSuccessful = FJsonSerializer::Serialize(ContentJson.ToSharedRef(), JsonWriter); - - // Check if serialization was successful - if (bSerializationSuccessful) - { - UNakamaSession* ResultSession = NewObject(); - ResultSession->SetupSession(JsonString); - return ResultSession; - } - else - { - NAKAMA_LOG_ERROR("Restore Session: Failed to serialize Restore Session JSON object"); - } - return nullptr; -} - -bool UNakamaSession::ParseJwtPayload(const FString& jwt, TSharedPtr& payloadJson) -{ - // Split the JWT into its three parts - TArray jwtParts; - jwt.ParseIntoArray(jwtParts, TEXT(".")); - - if (jwtParts.Num() != 3) - { - // Invalid JWT format - return false; - } - - // Convert Base64Url to Base64 - FString payloadString = jwtParts[1]; - payloadString.ReplaceInline(TEXT("-"), TEXT("+")); - payloadString.ReplaceInline(TEXT("_"), TEXT("/")); - - // Handle padding - int32 mod = payloadString.Len() % 4; - if (mod != 0) { - for (int32 i = 0; i < (4 - mod); ++i) { - payloadString += TEXT("="); - } - } - - // Decode to bytes - TArray decodedBytes; - FBase64::Decode(payloadString, decodedBytes); - - // Ensure null termination - decodedBytes.Add(0); - - // Convert UTF-8 bytes to FString to handle special characters - FString decodedPayloadString = FString(UTF8_TO_TCHAR(reinterpret_cast(decodedBytes.GetData()))); - - // Parse the decoded payload as a JSON object - TSharedRef> reader = TJsonReaderFactory<>::Create(decodedPayloadString); - if (!FJsonSerializer::Deserialize(reader, payloadJson)) - { - // Failed to parse JSON - return false; - } - - return true; -} - diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaStatus.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaStatus.cpp deleted file mode 100644 index 62804755a..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaStatus.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaStatus.h" - -#include "NakamaUtils.h" -#include "NakamaAccount.h" - -FNakamaStatus::FNakamaStatus(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TSharedPtr* StatusJsonObject; - if (JsonObject->TryGetObjectField(TEXT("status"), StatusJsonObject)) - { - const TArray>* PresencesJsonArray; - if ((*StatusJsonObject)->TryGetArrayField(TEXT("presences"), PresencesJsonArray)) - { - for (const TSharedPtr& PresenceJson : *PresencesJsonArray) - { - if (TSharedPtr PresenceJsonObject = PresenceJson->AsObject()) - { - Presences.Add(FNakamaUserPresence(PresenceJsonObject)); - } - } - } - } - } -} - -FNakamaStatus::FNakamaStatus() -{ - -} - -FNakamaStatusPresenceEvent::FNakamaStatusPresenceEvent(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* JoinsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("joins"), JoinsJsonArray)) - { - for (const TSharedPtr& UserPresence : *JoinsJsonArray) - { - if (TSharedPtr UserPresenceJsonObject = UserPresence->AsObject()) - { - Joins.Add(FNakamaUserPresence(UserPresenceJsonObject)); - } - } - } - - const TArray>* LeavesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("leaves"), LeavesJsonArray)) - { - for (const TSharedPtr& UserPresence : *LeavesJsonArray) - { - if (TSharedPtr UserPresenceJsonObject = UserPresence->AsObject()) - { - Leaves.Add(FNakamaUserPresence(UserPresenceJsonObject)); - } - } - } - } -} - - -FNakamaStatusPresenceEvent::FNakamaStatusPresenceEvent() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaStorageObject.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaStorageObject.cpp deleted file mode 100644 index fcee237f3..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaStorageObject.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaStorageObject.h" -#include "NakamaUtils.h" - -FNakamaStoreObjectData::FNakamaStoreObjectData(const FString& JsonString) : FNakamaStoreObjectData(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaStoreObjectData::FNakamaStoreObjectData(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("collection"), Collection); - JsonObject->TryGetStringField(TEXT("key"), Key); - JsonObject->TryGetStringField(TEXT("user_id"), UserId); - JsonObject->TryGetStringField(TEXT("value"), Value); - JsonObject->TryGetStringField(TEXT("version"), Version); - - int32 PermissionReadValue; - if (JsonObject->TryGetNumberField(TEXT("permission_read"), PermissionReadValue)) - { - PermissionRead = static_cast(PermissionReadValue); - } - - int32 PermissionWriteValue; - if (JsonObject->TryGetNumberField(TEXT("permission_write"), PermissionWriteValue)) - { - PermissionWrite = static_cast(PermissionWriteValue); - } - - FString CreatedAtString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreatedAtString)) - { - FDateTime::ParseIso8601(*CreatedAtString, CreateTime); - } - - FString UpdatedAtString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdatedAtString)) - { - FDateTime::ParseIso8601(*UpdatedAtString, UpdateTime); - } - } -} - -FNakamaStoreObjectData::FNakamaStoreObjectData(): PermissionRead(ENakamaStoragePermissionRead::NO_READ), PermissionWrite(ENakamaStoragePermissionWrite::NO_WRITE) -{ - -} - -FNakamaStoreObjectWrite::FNakamaStoreObjectWrite(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("collection"), Collection); - JsonObject->TryGetStringField(TEXT("key"), Key); - JsonObject->TryGetStringField(TEXT("value"), Value); - JsonObject->TryGetStringField(TEXT("version"), Version); - - int32 PermissionReadValue; - if (JsonObject->TryGetNumberField(TEXT("permission_read"), PermissionReadValue)) - { - PermissionRead = static_cast(PermissionReadValue); - } - - int32 PermissionWriteValue; - if (JsonObject->TryGetNumberField(TEXT("permission_write"), PermissionWriteValue)) - { - PermissionWrite = static_cast(PermissionWriteValue); - } - } -} - -FNakamaStoreObjectWrite::FNakamaStoreObjectWrite(): PermissionRead(), PermissionWrite() -{ -} - -FNakamaReadStorageObjectId::FNakamaReadStorageObjectId(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("collection"), Collection); - JsonObject->TryGetStringField(TEXT("key"), Key); - JsonObject->TryGetStringField(TEXT("user_id"), UserId); - } -} - -FNakamaReadStorageObjectId::FNakamaReadStorageObjectId() -{ - -} - -FNakamaDeleteStorageObjectId::FNakamaDeleteStorageObjectId(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("collection"), Collection); - JsonObject->TryGetStringField(TEXT("key"), Key); - JsonObject->TryGetStringField(TEXT("version"), Version); - } -} - -FNakamaDeleteStorageObjectId::FNakamaDeleteStorageObjectId() -{ - -} - -FNakamaStoreObjectAck::FNakamaStoreObjectAck(const FString& JsonString) : FNakamaStoreObjectAck(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaStoreObjectAck::FNakamaStoreObjectAck(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("collection"), Collection); - JsonObject->TryGetStringField(TEXT("key"), Key); - JsonObject->TryGetStringField(TEXT("version"), Version); - JsonObject->TryGetStringField(TEXT("user_id"), UserId); - } -} - - -FNakamaStoreObjectAck::FNakamaStoreObjectAck() -{ - -} - -FNakamaStoreObjectAcks::FNakamaStoreObjectAcks(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* StorageJobjectsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("acks"), StorageJobjectsJsonArray)) - { - for (const TSharedPtr& StorageJson : *StorageJobjectsJsonArray) - { - if (TSharedPtr StorageJsonObject = StorageJson->AsObject()) - { - StorageObjects.Add(FNakamaStoreObjectAck(StorageJsonObject)); - } - } - } - } -} - - -FNakamaStoreObjectAcks::FNakamaStoreObjectAcks() -{ - -} - -FNakamaStorageObjectList::FNakamaStorageObjectList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* StorageJobjectsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("objects"), StorageJobjectsJsonArray)) - { - for (const TSharedPtr& StorageJson : *StorageJobjectsJsonArray) - { - if (TSharedPtr StorageJsonObject = StorageJson->AsObject()) - { - Objects.Add(FNakamaStoreObjectData(StorageJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - -FNakamaStorageObjectList::FNakamaStorageObjectList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaStreams.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaStreams.cpp deleted file mode 100644 index d233c135e..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaStreams.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaStreams.h" -#include "NakamaUtils.h" - -FNakamaStream::FNakamaStream(const FString& JsonString) : FNakamaStream(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaStream::FNakamaStream(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetNumberField(TEXT("mode"), Mode); - JsonObject->TryGetStringField(TEXT("subject"), Subject); - JsonObject->TryGetStringField(TEXT("subcontext"), SubContext); - JsonObject->TryGetStringField(TEXT("label"), Label); - } -} - -FNakamaStream::FNakamaStream() -{ - -} - -FNakamaStreamData::FNakamaStreamData(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TSharedPtr* StreamJsonObject; - if (JsonObject->TryGetObjectField(TEXT("stream"), StreamJsonObject)) - { - Stream = FNakamaStream(*StreamJsonObject); - } - - const TSharedPtr* SenderJsonObject; - if (JsonObject->TryGetObjectField(TEXT("sender"), SenderJsonObject)) - { - Sender = FNakamaUserPresence(*SenderJsonObject); - } - - JsonObject->TryGetStringField(TEXT("data"), Data); - } -} - -FNakamaStreamData::FNakamaStreamData() -{ - -} - -FNakamaStreamPresenceEvent::FNakamaStreamPresenceEvent(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TSharedPtr* StreamJsonObject; - if (JsonObject->TryGetObjectField(TEXT("stream"), StreamJsonObject)) - { - Stream = FNakamaStream(*StreamJsonObject); - } - - const TArray>* JoinsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("joins"), JoinsJsonArray)) - { - for (const TSharedPtr& JoinJsonValue : *JoinsJsonArray) - { - if (TSharedPtr JoinJsonObject = JoinJsonValue->AsObject()) - { - Joins.Add(FNakamaUserPresence(JoinJsonObject)); - } - } - } - - const TArray>* LeavesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("leaves"), LeavesJsonArray)) - { - for (const TSharedPtr& LeaveJsonValue : *LeavesJsonArray) - { - if (TSharedPtr LeaveJsonObject = LeaveJsonValue->AsObject()) - { - Leaves.Add(FNakamaUserPresence(LeaveJsonObject)); - } - } - } - - } -} - -FNakamaStreamPresenceEvent::FNakamaStreamPresenceEvent() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaTournament.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaTournament.cpp deleted file mode 100644 index e0ba36b84..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaTournament.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaTournament.h" -#include "NakamaUtils.h" - -FNakamaTournament::FNakamaTournament(const FString& JsonString) : FNakamaTournament(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaTournament::FNakamaTournament(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("title"), Title); - JsonObject->TryGetStringField(TEXT("description"), Description); - JsonObject->TryGetNumberField(TEXT("category"), Category); - JsonObject->TryGetNumberField(TEXT("sort_order"), SortOrder); - JsonObject->TryGetNumberField(TEXT("size"), Size); - JsonObject->TryGetNumberField(TEXT("max_size"), MaxSize); - JsonObject->TryGetNumberField(TEXT("max_num_score"), MaxNumScore); - JsonObject->TryGetBoolField(TEXT("can_enter"), CanEnter); - - FString CreateTimeString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreateTimeString)) - { - FDateTime::ParseIso8601(*CreateTimeString, CreateTime); - } - - FString StartTimeString; - if (JsonObject->TryGetStringField(TEXT("start_time"), StartTimeString)) - { - FDateTime::ParseIso8601(*StartTimeString, StartTime); - } - - FString EndTimeString; - if (JsonObject->TryGetStringField(TEXT("end_time"), EndTimeString)) - { - FDateTime::ParseIso8601(*EndTimeString, EndTime); - } - - JsonObject->TryGetNumberField(TEXT("end_active"), EndActive); - JsonObject->TryGetNumberField(TEXT("next_reset"), NextReset); - JsonObject->TryGetNumberField(TEXT("duration"), Duration); - JsonObject->TryGetNumberField(TEXT("start_active"), StartActive); - - JsonObject->TryGetStringField(TEXT("metadata"), Metadata); - } -} - -FNakamaTournament::FNakamaTournament() -{ - -} - -FNakamaTournamentRecordList::FNakamaTournamentRecordList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* RecordsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("records"), RecordsJsonArray)) - { - for (const TSharedPtr& RecordJson : *RecordsJsonArray) - { - if (TSharedPtr RecordJsonObject = RecordJson->AsObject()) - { - Records.Add(FNakamaLeaderboardRecord(RecordJsonObject)); - } - } - } - - const TArray>* OwnerRecordsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("owner_records"), OwnerRecordsJsonArray)) - { - for (const TSharedPtr& OwnerRecordJson : *OwnerRecordsJsonArray) - { - if (TSharedPtr OwnerRecordJsonObject = OwnerRecordJson->AsObject()) - { - OwnerRecords.Add(FNakamaLeaderboardRecord(OwnerRecordJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("next_cursor"), NextCursor); - JsonObject->TryGetStringField(TEXT("prev_cursor"), PrevCursor); - } - -} - -FNakamaTournamentRecordList::FNakamaTournamentRecordList() -{ - -} - -FNakamaTournamentList::FNakamaTournamentList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* TournamentsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("tournaments"), TournamentsJsonArray)) - { - for (const TSharedPtr& TournamentJson : *TournamentsJsonArray) - { - if (TSharedPtr TournamentJsonObject = TournamentJson->AsObject()) - { - Tournaments.Add(FNakamaTournament(TournamentJsonObject)); - } - } - } - - JsonObject->TryGetStringField(TEXT("cursor"), Cursor); - } -} - - -FNakamaTournamentList::FNakamaTournamentList() -{ - -} diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaUnreal.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaUnreal.cpp deleted file mode 100644 index a4aa967d1..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaUnreal.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaUnreal.h" -#include "Modules/ModuleManager.h" - -void FNakamaUnrealModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - - //NLogger::init(std::make_shared(), NLogLevel::Debug); - -} - -void FNakamaUnrealModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FNakamaUnrealModule, NakamaUnreal) diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaUser.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaUser.cpp deleted file mode 100644 index 36b48835e..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaUser.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaUser.h" -#include "NakamaUtils.h" - -FNakamaUserList::FNakamaUserList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* UsersJsonArray; - if (JsonObject->TryGetArrayField(TEXT("users"), UsersJsonArray)) - { - for (const TSharedPtr& UserJson : *UsersJsonArray) - { - if (TSharedPtr UserJsonObject = UserJson->AsObject()) - { - Users.Add(FNakamaUser(UserJsonObject)); - } - } - } - } -} - - -FNakamaUser::FNakamaUser(const FString& JsonString) : FNakamaUser(FNakamaUtils::DeserializeJsonObject(JsonString)) { -} - -FNakamaUser::FNakamaUser(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("id"), Id); - JsonObject->TryGetStringField(TEXT("username"), Username); - JsonObject->TryGetStringField(TEXT("display_name"), DisplayName); - JsonObject->TryGetStringField(TEXT("avatar_url"), AvatarUrl); - JsonObject->TryGetStringField(TEXT("lang_tag"), Language); - JsonObject->TryGetStringField(TEXT("location"), Location); - JsonObject->TryGetStringField(TEXT("timezone"), TimeZone); - JsonObject->TryGetStringField(TEXT("metadata"), MetaData); - JsonObject->TryGetStringField(TEXT("facebook_id"), FacebookId); - JsonObject->TryGetStringField(TEXT("google_id"), GoogleId); - JsonObject->TryGetStringField(TEXT("gamecenter_id"), GameCenterId); - JsonObject->TryGetStringField(TEXT("apple_id"), AppleId); - JsonObject->TryGetStringField(TEXT("steam_id"), SteamId); - - JsonObject->TryGetBoolField(TEXT("online"), Online); - JsonObject->TryGetNumberField(TEXT("edge_count"), EdgeCount); - - FString CreatedAtString; - if (JsonObject->TryGetStringField(TEXT("create_time"), CreatedAtString)) - { - FDateTime::ParseIso8601(*CreatedAtString, CreatedAt); - } - - FString UpdatedAtString; - if (JsonObject->TryGetStringField(TEXT("update_time"), UpdatedAtString)) - { - FDateTime::ParseIso8601(*UpdatedAtString, updatedAt); - } - } -} - diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaUserSession.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaUserSession.cpp deleted file mode 100644 index 86a527012..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaUserSession.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaUserSession.h" - -FNakamaUserSession::FNakamaUserSession(): IsCreated(false), CreateTime(FDateTime::MinValue()), ExpireTime(FDateTime::MinValue()), IsExpired(false), IsRefreshExpired(false) -{ - -} \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Private/NakamaUtils.cpp b/Nakama/Source/NakamaUnreal/Private/NakamaUtils.cpp deleted file mode 100644 index c24ae37ab..000000000 --- a/Nakama/Source/NakamaUnreal/Private/NakamaUtils.cpp +++ /dev/null @@ -1,195 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "NakamaUtils.h" -#include "NakamaUser.h" -#include "NakamaLogger.h" -#include "Dom/JsonObject.h" -#include "Misc/EngineVersionComparison.h" -#include "Interfaces/IHttpResponse.h" - -DEFINE_LOG_CATEGORY_STATIC(LogNakamaUtils, Log, Log); - -void FNakamaUtils::ProcessRequestComplete(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, const TFunction& SuccessCallback, const TFunction& ErrorCallback) -{ - if (bSuccess && Response.IsValid()) - { - const int32 ResponseCode = Response->GetResponseCode(); - const FString ResponseBody = Response->GetContentAsString(); - - if (ResponseCode == 200) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Request Successful: %s"), *ResponseBody)); - if (SuccessCallback) - { - SuccessCallback(ResponseBody); - } - } - else - { - NAKAMA_LOG_WARN(FString::Printf(TEXT("Response (Code: %d) - Contents: %s"), ResponseCode, *ResponseBody)); - const FNakamaError Error(ResponseBody); - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - } - else - { - // Handle request failure - NAKAMA_LOG_ERROR(TEXT("Failed to process request.")); - - if (Request.IsValid()) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Request URL: %s"), *(Request->GetURL()))); - } - - FNakamaError Error; - Error.Code = ENakamaErrorCode::Unknown; - Error.Message = TEXT("Failed to proccess request. Request failed."); - - if (ErrorCallback) - { - ErrorCallback(Error); - } - } -} - -void FNakamaUtils::ProcessRequestCompleteMove(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, - const TFunction& SuccessCallback, const TFunction& ErrorCallback) -{ - if (bSuccess && Response.IsValid()) - { - const int32 ResponseCode = Response->GetResponseCode(); - FString ResponseBody = Response->GetContentAsString(); - - if (ResponseCode == 200) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Request Successful: %s"), *ResponseBody)); - if (SuccessCallback) - { - SuccessCallback(MoveTemp(ResponseBody)); - } - } - else - { - NAKAMA_LOG_WARN(FString::Printf(TEXT("Response (Code: %d) - Contents: %s"), ResponseCode, *ResponseBody)); - const FNakamaError Error(ResponseBody); - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - } - else - { - // Handle request failure - NAKAMA_LOG_ERROR(TEXT("Failed to process request.")); - - if (Request.IsValid()) - { - NAKAMA_LOG_DEBUG(FString::Printf(TEXT("Request URL: %s"), *(Request->GetURL()))); - } - - FNakamaError Error; - Error.Code = ENakamaErrorCode::Unknown; - Error.Message = TEXT("Failed to proccess request. Request failed."); - - if (ErrorCallback) - { - ErrorCallback(Error); - } - } -} - -void FNakamaUtils::HandleJsonSerializationFailure(TFunction ErrorCallback) - { - NAKAMA_LOG_ERROR(TEXT("Failed to generate request content.")); - FNakamaError Error; - Error.Code = ENakamaErrorCode::Unknown; - Error.Message = TEXT("Failed to generate request content."); - ErrorCallback(Error); - } - - bool FNakamaUtils::IsSessionValid(const UNakamaSession* Session, TFunction ErrorCallback) - { - if (!Session || Session->SessionData.AuthToken.IsEmpty()) - { - NAKAMA_LOG_ERROR("Invalid session or session data."); - - FNakamaError Error; - Error.Message = "Invalid session or session data."; - ErrorCallback(Error); - return false; - } - - return true; - } - - bool FNakamaUtils::IsResponseSuccessful(int32 ResponseCode) - { - return ResponseCode == 200; - } - - FNakamaError FNakamaUtils::CreateRequestFailureError() - { - NAKAMA_LOG_ERROR(TEXT("Failed to proccess request. Request failed.")); - FNakamaError Error; - Error.Code = ENakamaErrorCode::Unknown; - Error.Message = TEXT("Failed to proccess request. Request failed."); - return Error; - } - - TSharedRef FNakamaUtils::MakeRequest(const FString& URL, const FString& Content, ENakamaRequestMethod RequestMethod, const FString& SessionToken, float Timeout) - { - FHttpModule* HttpModule = &FHttpModule::Get(); - - // Create the HttpRequest -#if ENGINE_MAJOR_VERSION <= 4 && ENGINE_MINOR_VERSION <= 25 - TSharedRef Request = HttpModule->CreateRequest(); -#else - TSharedRef HttpRequest = HttpModule->CreateRequest(); -#endif - - HttpRequest->SetURL(URL); - HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - HttpRequest->SetTimeout(Timeout); // Exposed to end user - - FString VerbString = ENakamaRequesMethodToFString(RequestMethod); - if (!VerbString.IsEmpty()) - { - HttpRequest->SetVerb(VerbString); - } - - // Set the content if it is not empty - if (!Content.IsEmpty()) - { - HttpRequest->SetContentAsString(Content); - } - - // Add authorization header if session token is provided - if (!SessionToken.IsEmpty()) - { - FString AuthorizationHeader = FString::Printf(TEXT("Bearer %s"), *SessionToken); - HttpRequest->SetHeader(TEXT("Authorization"), AuthorizationHeader); - } - - //NAKAMA_LOG_INFO(TEXT("...")); - //NAKAMA_LOG_INFO(FString::Printf(TEXT("Making Request to %s"), *Endpoint)); - NAKAMA_LOG_INFO(FString::Printf(TEXT("Making %s request to %s with content: %s"), *VerbString, *URL, *Content)); - return HttpRequest; - } diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaAccount.h b/Nakama/Source/NakamaUnreal/Public/NakamaAccount.h deleted file mode 100644 index c58fb5565..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaAccount.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaAccountDevice.h" -#include "NakamaUser.h" -#include "NakamaAccount.generated.h" - -namespace Nakama -{ - struct NAccount; -} - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaAccount -{ - GENERATED_BODY() - - // The user object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "user")) - FNakamaUser User; - - // The user's wallet data. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "wallet")) - FString Wallet; - - // The email address of the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "email")) - FString Email; - - // The devices which belong to the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "devices")) - TArray Devices; - - // The custom id in the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "custom_id")) - FString CustomId; - - // The UNIX time when the user's email was verified. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "verify_time")) - FDateTime VerifyTime; - - // The UNIX time when the user's account was disabled/banned. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account", meta = (JsonProperty = "disable_time")) - FDateTime DisableTime; - - FNakamaAccount(); - - FNakamaAccount(const FString& JsonString); - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaAccountDevice.h b/Nakama/Source/NakamaUnreal/Public/NakamaAccountDevice.h deleted file mode 100644 index d315ee3c6..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaAccountDevice.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaAccountDevice.generated.h" - - -// Used with authenticate/link/unlink and user. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaAccountDevice -{ - GENERATED_BODY() - - // A device identifier. Should be obtained by a platform-specific device API. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account") - FString Id; - - // Extra information that will be bundled in the session token. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Account") - TMap Vars; - - FNakamaAccountDevice(const FString& JsonString); - FNakamaAccountDevice(const TSharedPtr JsonObject); - FNakamaAccountDevice(); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaChannelTypes.h b/Nakama/Source/NakamaUnreal/Public/NakamaChannelTypes.h deleted file mode 100644 index 1608e18f8..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaChannelTypes.h +++ /dev/null @@ -1,213 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" - -#include "NakamaChannelTypes.generated.h" - -//Chat Category// -UENUM(BlueprintType) -enum class ENakamaChannelType : uint8 -{ - // Default case. Assumed as ROOM type. - TYPE_UNSPECIFIED UMETA(DisplayName = "Unspecified"), - // A chat room which can be created dynamically with a name. - ROOM UMETA(DisplayName = "Room"), - // A private chat between two users. - DIRECT_MESSAGE UMETA(DisplayName = "Direct Message"), - // A chat within a group on the server. - GROUP UMETA(DisplayName = "Group"), -}; - -// A message sent on a channel. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChannelMessage -{ - GENERATED_BODY() - - // The channel this message belongs to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString ChannelId; - - // The unique ID of this message. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString MessageId; - - // The code representing a message type or category. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - int32 code = 0; - - // Message sender, usually a user ID. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString SenderId; - - // The username of the message sender, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString Username; - - // The content payload. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString Content; - - // The UNIX time when the message was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FDateTime CreateTime = 0; - - // The UNIX time when the message was last updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FDateTime UpdateTime = 0; - - // True if the message was persisted to the channel's history, false otherwise. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - bool Persistent = false; - - // The name of the chat room, or an empty string if this message was not sent through a chat room. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString RoomName; - - // The ID of the group, or an empty string if this message was not sent through a group channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString GroupId; - - // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdOne; - - // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdTwo; - - FNakamaChannelMessage(const FString& JsonString); - FNakamaChannelMessage(const TSharedPtr JsonObject); - FNakamaChannelMessage(); // Default Constructor -}; - -// A receipt reply from a channel message send operation. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChannelMessageAck -{ - GENERATED_BODY() - - // The channel the message was sent to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString ChannelId; - - // The unique ID assigned to the message. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString MessageId; - - // Username of the message sender. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString Username; - - // The code representing a message type or category. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - int32 code = 0; - - // The UNIX time when the message was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FDateTime CreateTime = 0; - - // The UNIX time when the message was last updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FDateTime UpdateTime = 0; - - // True if the message was persisted to the channel's history, false otherwise. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - bool Persistent = false; - - // The name of the chat room, or an empty string if this message was not sent through a chat room. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString RoomName; - - // The ID of the group, or an empty string if this message was not sent through a group channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString GroupId; - - // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdOne; - - // - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdTwo; - - FNakamaChannelMessageAck(const FString& JsonString); - FNakamaChannelMessageAck(); // Default Constructor -}; - -// A list of channel messages, usually a result of a list operation. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChannelMessageList -{ - GENERATED_BODY() - - // A list of messages. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - TArray Messages; - - // The cursor to send when retireving the next page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString NextCursor; - - // The cursor to send when retrieving the previous page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString PrevCursor; - - FNakamaChannelMessageList(const FString& JsonString); - FNakamaChannelMessageList(); - -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChannelPresenceEvent -{ - GENERATED_BODY() - - // The channel identifier this event is for. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString ChannelId; - - // Presences joining the channel as part of this event, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - TArray Joins; - - // Presences leaving the channel as part of this event, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - TArray Leaves; - - // The name of the chat room, or an empty string if this message was not sent through a chat room. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString RoomName; - - // The ID of the group, or an empty string if this message was not sent through a group channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString GroupId; - - // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdOne; - - // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Chat|Channel") - FString UserIdTwo; - - FNakamaChannelPresenceEvent(const FString& JsonString); - FNakamaChannelPresenceEvent(); // Default Constructor -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaChat.h b/Nakama/Source/NakamaUnreal/Public/NakamaChat.h deleted file mode 100644 index 8bb295385..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaChat.h +++ /dev/null @@ -1,109 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaChannelTypes.h" -#include "NakamaPresence.h" -#include "NakamaChat.generated.h" - -namespace Nakama -{ - struct NChannel; -} - -// For sending messages (Internal to plugin, no need to convert) -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChatMessage -{ - GENERATED_BODY() - - // The Chat Message. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama Chat") - FString ChatMessage; - - // The Message Type. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama Chat") - ENakamaChannelType MessageType = ENakamaChannelType::TYPE_UNSPECIFIED; - - // Group Name to Send To. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama Chat") - FString GroupName; - - FNakamaChatMessage() : ChatMessage(TEXT("")), MessageType(ENakamaChannelType::DIRECT_MESSAGE), GroupName(TEXT("")) - { } -}; - - -// Channels -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaChannel -{ - GENERATED_BODY() - - // The ID of the channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - FString Id; - - // The users currently in the channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - TArray Presences; - - // A reference to the current user's presence in the channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, DisplayName = "Self", Category = "Nakama Chat") - FNakamaUserPresence Me; - - // The name of the chat room, or an empty string if this message was not sent through a chat room. - //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat", meta = (JsonProperty = "room_name")) - FString RoomName; - - // The ID of the group, or an empty string if this message was not sent through a group channel. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat", meta = (JsonProperty = "group_id")) - FString GroupId; - - // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat", meta = (JsonProperty = "user_id_one")) - FString UserIdOne; - - // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat", meta = (JsonProperty = "user_id_two")) - FString UserIdTwo; - - FNakamaChannel(const FString& JsonString); - FNakamaChannel(); -}; - -// For sending messages (Internal to plugin, no need to convert) -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPrivateChat -{ - GENERATED_BODY() - - // User Id One - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - FString UserIdOne; - - // User Id Two - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - FString UserIdTwo; - - // Channel Id. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Chat") - FString ChannelId; - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaClient.h b/Nakama/Source/NakamaUnreal/Public/NakamaClient.h deleted file mode 100644 index 1879c213f..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaClient.h +++ /dev/null @@ -1,3048 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRealtimeClient.h" -#include "NakamaUser.h" -#include "NakamaAccount.h" -#include "NakamaFriend.h" -#include "NakamaGroup.h" -#include "NakamaError.h" -#include "NakamaNotification.h" -#include "NakamaStorageObject.h" -#include "NakamaLeaderboard.h" -#include "NakamaTournament.h" -#include "NakamaRPC.h" -#include "NakamaMatch.h" -#include "NakamaSession.h" -#include "Interfaces/IHttpRequest.h" -#include "HttpModule.h" - -#include "NakamaClient.generated.h" - -using namespace Nakama; - -// Delegates -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAuthUpdate, UNakamaSession*, LoginData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnError, const FNakamaError&, ErrorData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLinkSuccess); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUnLinkSuccess); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAuthRefresh, UNakamaSession*, Session); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAuthRefreshError, const FNakamaError&, ErrorData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUserAccountInfo, const FNakamaAccount&, AccountData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetUsers, const TArray &, Users); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUpdateAccount); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeleteUser); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFriendsList, FNakamaFriendList, Friends); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedFriendsList, FNakamaFriendList, list); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAddedFriend); //Add 's here -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRemovedFriends); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnBlockedFriends); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnJoinGroup); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRemoveGroup); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListGroupMembers, const FNakamaGroupUsersList&, members); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUpdateGroup); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLeaveGroup); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAddGroupUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPromoteGroupUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDemoteGroupUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnKickGroupUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnBanGroupUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUserGroups, const FNakamaUserGroupList&, UserGroups); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGroupsList, const FNakamaGroupList&, Groups); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCreateGroup, FNakamaGroup, Group); //FOnStorageObjectAcks -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnJoinedGroup); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListNotifications, FNakamaNotificationList, NotificationList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeleteNotifications); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStorageObjectAcks, const FNakamaStoreObjectAcks&, StorageObjectsAcks); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStorageObjectsRead, const FNakamaStorageObjectList&, StorageObjects); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStorageObjectsListed, const FNakamaStorageObjectList&, StorageObjects); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRemovedStorageObjects); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListedParties, const FNakamaPartyList&, PartyList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRPC, const FNakamaRPC&, rpc); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListChannelMessages, const FNakamaChannelMessageList&, MessageList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWriteLeaderboardRecord, const FNakamaLeaderboardRecord&, Record); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListLeaderboardRecords, const FNakamaLeaderboardRecordList&, RecordsList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeletedLeaderboardRecord); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListTournamentRecords, const FNakamaTournamentRecordList&, RecordsList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListTournaments, const FNakamaTournamentList&, Tournaments); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnJoinedTournament); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMatchlist, const FNakamaMatchList&, MatchList); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnImportFacebookFriends); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnImportSteamFriends); - -UENUM(BlueprintType) -enum class ENakamaRequestMethod : uint8 -{ - GET, - POST, - DEL, - PUT, -}; - -/** - * - */ -UCLASS(Blueprintable, BlueprintType, meta=(BlueprintSpawnableComponent)) -class NAKAMAUNREAL_API UNakamaClient : public UObject -{ - GENERATED_BODY() - -private: - - // End user can set this - float Timeout = 10.0f; - -protected: - - // Variables - FString Hostname; - int32 Port; - FString ServerKey; - bool bUseSSL; - -public: - - void InitializeClient( - const FString& InHostname, - int32 InPort, - const FString& InServerKey, - bool bInUseSSL - ); - - bool bEnableDebug; - - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events") - FOnDisconnected DisconnectedEvent; - - UPROPERTY() - bool bIsActive; - - // Initialize System, this has to be called first, done via the Library Action instead (removed BlueprintCallable) - UFUNCTION(Category = "Nakama|Initialize") - void InitializeSystem(const FString& InServerKey, const FString& Host, int32 InPort, bool UseSSL, bool EnableDebug); - - /** - * Disconnects the client. This function kills all outgoing exchanges immediately without waiting. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Client", meta=(DeprecatedFunction, DeprecationMessage="Use CancelAllRequests instead")) - void Disconnect(); - - /** - * Cancels all Requests. This function kills all outgoing exchanges immediately without waiting. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - void CancelAllRequests(); - - /** - * Destroys the Client. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - void Destroy(); - - // Manage Timeout - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - void SetTimeout(float InTimeout); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - float GetTimeout(); - - - // Event that is called on cleanup - virtual void BeginDestroy() override; - - /** - * Creates a default client to interact with Nakama server. - * - * @param ServerKey Server key should match the one on the Nakama Server. - * @param Host The endpoint host name. - * @param Port The port to use, default is 7350. - * @param UseSSL Use "https" scheme if you've setup SSL. - * @param EnableDebug To enable logs output to console with debug logging level. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - static UNakamaClient* CreateDefaultClient( - const FString& ServerKey = "defaultkey", - const FString& Host = "localhost", - int32 Port = 7350, - bool UseSSL = false, - bool EnableDebug = true - ); - - // --- Authentication --- // - - /** - * Authenticate a user with a custom id. - * - * @param UserID A custom identifier usually obtained from an external authentication service. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateCustom( - const FString& UserID, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with an email and password. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateEmail( - const FString& Email, - const FString& Password, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with a device id. - * - * @param DeviceID A device identifier usually obtained from a platform API. - * @param Username A username used to create the user. Defaults to empty string. - * @param CreateAccount True if the user should be created when authenticated. Defaults to false. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateDevice( - const FString& DeviceID, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - const FOnAuthUpdate& Success, - const FOnError& Error - ); - - // Social Authentication - - /** - * Authenticate a user with a Steam auth token. - * - * @param SteamToken An authentication token from the Steam network. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param ImportFriends True if the Steam friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateSteam( - const FString& SteamToken, - const FString& Username, - bool CreateAccount, - bool ImportFriends, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with a Google auth token. - * - * @param AccessToken An OAuth access token from the Google SDK. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateGoogle( - const FString& AccessToken, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with Apple Game Center. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateGameCenter( - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with a Facebook auth token. - * - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param ImportFriends True if the Facebook friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateFacebook( - const FString& AccessToken, - const FString& Username, - bool CreateAccount, - bool ImportFriends, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Authenticate a user with Apple Sign In. - * - * @param Token The ID token received from Apple to validate. - * @param Username A username used to create the user. - * @param CreateAccount True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateApple( - const FString& Token, - const FString& Username, - bool CreateAccount, - const TMap& Vars, - FOnAuthUpdate Success, - FOnError Error - ); - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param Session The session of the user. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - **/ - UFUNCTION(Category = "Nakama|Authentication") - void AuthenticateRefresh( - UNakamaSession* Session, - FOnAuthUpdate Success, - FOnError Error - ); - - // --- Restore Session --- // - - /** - * Restore User Session - * - * @param Token Authentication Token from Session - * @param RefreshToken RefreshToken retrieved from Session - * @param RestoredSession Returns restored session - */ - UFUNCTION(Category = "Nakama|Authentication") - void RestoreSession( - const FString& Token, - const FString& RefreshToken, - UNakamaSession*& RestoredSession - ); - - // --- Link Account --- // - - /** - * Link a custom ID to the user account associated with the provided session. - * - * @param CustomId A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user whose account will be linked to the custom ID. - * @param Success Delegate called when the custom ID is successfully linked, providing the updated session information. - * @param Error Delegate called if the linking process fails, detailing the error. - * @param Success Delegate called when the custom ID is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkCustom( - UNakamaSession *Session, - const FString& CustomId, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link a device ID to the user account associated with the provided session. - * - * @param DeviceId A device identifier usually obtained from a platform API. - * @param Session The session of the user whose account will be linked to the device ID. - * @param Success Delegate called when the device ID is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkDevice( - UNakamaSession *Session, - const FString& DeviceId, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link an email with password to the user account associated with the provided session. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user whose account will be linked to the email. - * @param Success Delegate called when the email is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkEmail( - UNakamaSession *Session, - const FString& Email, - const FString& Password, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link a Facebook profile to the user account associated with the provided session. - * - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param ImportFriends True if the Facebook friends should be imported. - * @param Session The session of the user whose account will be linked to the Facebook profile. - * @param Success Delegate called when the Facebook profile is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkFacebook( - UNakamaSession *Session, - const FString& AccessToken, - bool ImportFriends, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link a Game Center profile to the user account associated with the provided session. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user whose account will be linked to the Game Center profile. - * @param Success Delegate called when the Game Center profile is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkGameCenter( - UNakamaSession *Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link a Google profile to the user account associated with the provided session. - * - * @param AccessToken An OAuth access token from the Google SDK. - * @param Session The session of the user whose account will be linked to the Google profile. - * @param Success Delegate called when the Google profile is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkGoogle( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link a Steam profile to the user account associated with the provided session. - * - * @param SteamToken An authentication token from the Steam network. - * @param Session The session of the user whose account will be linked to the Steam profile. - * @param Success Delegate called when the Steam profile is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkSteam( - UNakamaSession *Session, - const FString& SteamToken, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Link an Apple ID to the user account associated with the provided session. - * - * @param Token The ID token received from Apple. - * @param Session The session of the user whose account will be linked to the Apple ID. - * @param Success Delegate called when the Apple ID is successfully linked. - * @param Error Delegate called if the linking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Link") - void LinkApple( - UNakamaSession *Session, - const FString& Token, - FOnLinkSuccess Success, - FOnError Error - ); - - // --- Unlinking --- // - - /** - * Unlink a custom ID from the user account associated with the provided session. - * - * @param CustomId A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user. - * @param Success Delegate called when the custom ID is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkCustom( - UNakamaSession *Session, - const FString& CustomId, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink a device ID from the user account associated with the provided session. - * - * @param DeviceId A device identifier usually obtained from a platform API. - * @param Session The session of the user. - * @param Success Delegate called when the device ID is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkDevice( - UNakamaSession *Session, - const FString& DeviceId, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink an email and its associated password from the user account tied to the session. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user. - * @param Success Delegate called when the email is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkEmail( - UNakamaSession *Session, - const FString& Email, - const FString& Password, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink a Facebook profile from the user account associated with the provided session. - * - * @param AccessToken An OAuth access token from the Facebook SDK. - * @param Session The session of the user. - * @param Success Delegate called when the Facebook profile is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkFacebook( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink a Game Center profile from the user account tied to the session. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimeStampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user. - * @param Success Delegate called when the Game Center profile is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkGameCenter( - UNakamaSession *Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimeStampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink a Google profile from the user account tied to the session. - * - * @param AccessToken An OAuth access token from the Google SDK. - * @param Session The session of the user. - * @param Success Delegate called when the Google profile is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkGoogle( - UNakamaSession *Session, - const FString& AccessToken, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink a Steam profile from the user account tied to the session. - * - * @param SteamToken An authentication token from the Steam network. - * @param Session The session of the user. - * @param Success Delegate called when the Steam profile is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkSteam( - UNakamaSession *Session, - const FString& SteamToken, - FOnLinkSuccess Success, - FOnError Error - ); - - /** - * Unlink an Apple profile from the user account associated with the provided session. - * - * @param Token The authentication token from Apple. - * @param Session The session of the user. - * @param Success Delegate called when the Apple profile is successfully unlinked. - * @param Error Delegate called if the unlinking process fails, detailing the error. - */ - UFUNCTION(Category = "Nakama|Authentication|Unlink") - void UnLinkApple( - UNakamaSession *Session, - const FString& Token, - FOnLinkSuccess Success, - FOnError Error - ); - - // --- Functions --- // - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param Session The session of the user. - * @param Success Delegate called when the user's session is successfully refreshed. Provides session information. - * @param Error Delegate called if the session refresh process fails, detailing the error. - **/ - UFUNCTION(Category = "Nakama|Authentication|Refresh") - void RefreshSession( - UNakamaSession *Session, - FOnAuthRefresh Success, - FOnAuthRefreshError Error - ); - - /** - * Import Facebook friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Facebook. This function can be used to be - * explicit with the import operation. - * - * @param Session The session of the user. - * @param Token An OAuth access token from the Facebook SDK. - * @param Reset True if the Facebook friend import for the user should be reset. - * @param Success Delegate called upon successful import of Facebook friends. - * @param Error Delegate called if the Facebook friends import operation fails, providing error details. - */ - UFUNCTION(Category = "Nakama|Friends") - void ImportFacebookFriends( - UNakamaSession* Session, - const FString& Token, - bool Reset, - FOnImportFacebookFriends Success, - FOnError Error - ); - - /** - * Import Steam friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Steam. This function can be used to be - * explicit with the import operation. - * - * @param Session The session of the user. - * @param SteamToken The Steam token to use. - * @param Reset True if the Steam friend import for the user should be reset. - * @param Success Delegate called upon successful import of Steam friends. - * @param Error Delegate called if the Steam friends import operation fails, providing error details. - */ - UFUNCTION(Category = "Nakama|Friends") - void ImportSteamFriends( - UNakamaSession* Session, - const FString& SteamToken, - bool Reset, - FOnImportSteamFriends Success, - FOnError Error - ); - - // --- Get Account and User Info --- // - - /** - * - * [DEPRECATED] Fetch the user account owned by the session - * @param Session The session of the user. - * @param Success Delegate called upon successfully retrieving user account details. - * @param Error Delegate called if the fetch operation for user account details fails, providing error information. - * - */ - UFUNCTION(Category = "Nakama|Users", meta=(DeprecatedFunction, DeprecationMessage="Use GetAccount instead")) - void GetUserAccount( - UNakamaSession *Session, - FOnUserAccountInfo Success, - FOnError Error - ); - - /** - * - * Fetch the user account owned by the session. - * @param Session The session of the user. - * @param Success Delegate called upon successfully retrieving user account details. - * @param Error Delegate called if the fetch operation for user account details fails, providing error information. - */ - UFUNCTION(Category = "Nakama|Users") - void GetAccount( - UNakamaSession *Session, - FOnUserAccountInfo Success, - FOnError Error - ); - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param UserIds List of user IDs. - * @param Usernames List of usernames. - * @param FacebookIds List of Facebook IDs. - * @param Session The session of the user. - * @param Success Delegate called upon successfully retrieving the users' details. - * @param Error Delegate called if the user fetch operation fails, providing error information. - * - */ - UFUNCTION(Category = "Nakama|Users") - void GetUsers( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - const TArray& FacebookIds, - FOnGetUsers Success, - FOnError Error - ); - - /** - * Update the current user's account on the server. - * - * @param Username The new username for the user. - * @param DisplayName A new display name for the user. - * @param AvatarUrl A new avatar url for the user. - * @param LanguageTag A new language tag in BCP-47 format for the user. - * @param Location A new location for the user. - * @param Timezone New timezone information for the user. - * @param Session The session of the user. - * @param Success Delegate called upon successfully updating the user's account details. - * @param Error Delegate called if the update operation fails, detailing the encountered error. - */ - UFUNCTION(Category = "Nakama|Users") - void UpdateAccount( - UNakamaSession *Session, - const FString& Username, - const FString& DisplayName, - const FString& AvatarUrl, - const FString& LanguageTag, - const FString& Location, - const FString& Timezone, - FOnUpdateAccount Success, - FOnError Error - ); - - /** - * Delete the current user from the server. - * - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting the user's account. - * @param Error Delegate called if the update operation fails, detailing the encountered error. - */ - UFUNCTION(Category = "Nakama|Users") - void DeleteUser( - UNakamaSession* Session, - FOnDeleteUser Success, - FOnError Error - ); - - // --- Realtime Client --- // - - /** - * Setup the Realtime Client (Socket) - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime") // Used for both Blueprints and C++ - UNakamaRealtimeClient* SetupRealtimeClient(); - - /** - * Fetch a list of matches active on the server. - * - * @param Session The session of the user. - * @param MinSize The minimum number of match participants. - * @param MaxSize The maximum number of match participants. - * @param Limit The number of matches to list. - * @param Label The label to filter the match list on. - * @param Query The query to the match listing. - * @param Authoritative True to include authoritative matches. - * @param Success Delegate called when the matches are fetched successfully, returning the match list. - * @param Error Delegate called if the fetching operation encounters an error, providing details on the failure. - */ - UFUNCTION(Category = "Nakama|Realtime") - void ListMatches( - UNakamaSession *Session, - int32 MinSize, - int32 MaxSize, - int32 Limit, - const FString& Label, - const FString& Query, - bool Authoritative, - FOnMatchlist Success, - FOnError Error - ); - - // --- Friends --- // - - /** - * [DEPRECATED] List of friends of the current user. - * - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Success Delegate called upon successful fetch, returning the list of friends. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Friends", meta=(DeprecatedFunction, DeprecationMessage="Use ListFriends instead")) - void GetFriends( - UNakamaSession* Session, - int32 Limit, - ENakamaFriendState State, - const FString& Cursor, - FOnFriendsList Success, - FOnError Error - ); - - /** - * List of friends of the current user. - * - * @param Limit The max number of records to return. Between 1 and 1000. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Success Delegate called upon successful fetch, returning the list of friends. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Friends") - void ListFriends( - UNakamaSession* Session, - int32 Limit, - ENakamaFriendState State, - const FString& Cursor, - FOnFriendsList Success, - FOnError Error - ); - - /** - * Add one or more friends by id. - * - * @param Ids The ids of the users to add or invite as friends. - * @param Usernames The usernames of the users to add as friends. - * @param Session The session of the user. - * @param Success Delegate called upon successful friend addition. - * @param Error Delegate called if an error occurs, detailing the failure. - * - */ - UFUNCTION(Category = "Nakama|Friends") - void AddFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnAddedFriend Success, - FOnError Error - ); - - /** - * [DEPRECATED] Delete one more or users by id or username from friends. - * - * @param Ids the user ids to remove as friends. - * @param Usernames The usernames to remove as friends. - * @param Session The session of the user. - * @param Success Delegate called upon successful friend removal. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Friends", meta=(DeprecatedFunction, DeprecationMessage="Use DeleteFriends instead")) - void RemoveFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnRemovedFriends Success, - FOnError Error - ); - - /** - * Delete one more or users by id or username from friends. - * - * @param Ids the user ids to remove as friends. - * @param Usernames The usernames to remove as friends. - * @param Session The session of the user. - * @param Success Delegate called upon successful friend removal. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Friends") - void DeleteFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnRemovedFriends Success, - FOnError Error - ); - - /** - * Block one or more friends by id. - * - * @param Ids The ids of the users to block. - * @param Usernames The usernames of the users to block. - * @param Session The session of the user. - * @param Success Delegate called upon successfully blocking friends. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Friends") - void BlockFriends( - UNakamaSession* Session, - const TArray& Ids, - const TArray& Usernames, - FOnBlockedFriends Success, - FOnError Error - ); - - // --- Groups --- // - - /** - * Create a group. - * - * @param GroupName The name for the group. - * @param Description A description for the group. - * @param AvatarUrl An avatar url for the group. - * @param LanguageTag A language tag in BCP-47 format for the group. - * @param Open True if the group should have open membership. - * @param MaxMembers Maximum number of group members. - * @param Session The session of the user. - * @param Success Delegate called upon successfully creating the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void CreateGroup( - UNakamaSession* Session, - const FString& GroupName, - const FString& Description, - const FString& AvatarUrl, - const FString& LanguageTag, - bool Open, - int32 MaxMembers, - FOnCreateGroup Success, - FOnError Error - ); - - /** - * List groups on the server. - * - * @param GroupNameFilter The name filter to apply to the group list. - * @param Limit The number of groups to list. - * @param Cursor A cursor for the current position in the groups to list. - * @param Session The session of the user. - * @param Success Delegate called upon successfully listing the groups. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void ListGroups( - UNakamaSession* Session, - const FString& GroupNameFilter, - int32 Limit, - const FString& Cursor, - FOnGroupsList Success, - FOnError Error - ); - - /** - * Join a group if it has open membership or request to join it. - * - * @param GroupId The id of the group to join. - * @param Session The session of the user. - * @param Success Delegate called upon successfully joining the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void JoinGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnJoinedGroup Success, - FOnError Error - ); - - /** - * List of groups the current user is a member of. - * @param UserId The id of the user whose groups to list. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The group state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Success Delegate called upon successfully listing the user's groups. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void ListUserGroups( - UNakamaSession* Session, - const FString& UserId, - int32 Limit, - ENakamaGroupState State, - const FString& Cursor, - FOnUserGroups Success, - FOnError Error - ); - - /** - * List all users part of the group. - * - * @param GroupId The id of the group. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The group state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param Success Delegate called upon successfully listing the group members. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void ListGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - int32 Limit, - ENakamaGroupState State, - const FString& Cursor, - FOnListGroupMembers Success, - FOnError Error - ); - - /** - * Update a group. - * - * The user must have the correct access permissions for the group. - * - * @param GroupId The id of the group to update. - * @param Name A new name for the group. - * @param Description A new description for the group. - * @param AvatarUrl A new avatar url for the group. - * @param LanguageTag A new language tag in BCP-47 format for the group. - * @param Open True if the group should have open membership. - * @param Session The session of the user. - * @param Success Delegate called upon successfully updating the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void UpdateGroup( - UNakamaSession* Session, - const FString& GroupId, - const FString& Name, - const FString& Description, - const FString& AvatarUrl, - const FString& LanguageTag, - bool Open, - FOnUpdateGroup Success, - FOnError Error - ); - - /** - * Leave a group by id. - * - * @param GroupId The id of the group to leave. - * @param Session The session of the user. - * @param Success Delegate called upon successfully leaving the group. - * @param Error Delegate called if an error occurs, detailing the failure. - * - */ - UFUNCTION(Category = "Nakama|Groups") - void LeaveGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnLeaveGroup Success, - FOnError Error - ); - - /** - * Add one or more users to the group. - * - * @param GroupId The id of the group to add users into. - * @param UserIds The ids of the users to add or invite to the group. - * @param Session The session of the user. - * @param Success Delegate called upon successfully adding users to the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void AddGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnAddGroupUsers Success, - FOnError Error - ); - - /** - * Promote a set of users in a group to the next role up. - * - * @param GroupId The group ID to promote in. - * @param UserIds The ids of the users to promote. - * @param Session The session of the user. - * @param Success Delegate called upon successfully promoting users in the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void PromoteGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnPromoteGroupUsers Success, - FOnError Error - ); - - /** - * Kick one or more users from the group. - * - * @param GroupId The id of the group. - * @param UserIds The ids of the users to kick. - * @param Session The session of the user. - * @param Success Delegate called upon successfully kicking users from the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void KickGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnKickGroupUsers Success, - FOnError Error - ); - - /** - * Ban one or more users from the group. - * - * @param GroupId The id of the group. - * @param UserIds The ids of the users to ban. - * @param Session The session of the user. - * @param Success Delegate called upon successfully banning users from the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void BanGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnBanGroupUsers Success, - FOnError Error - ); - - /** - * Demote a set of users in a group to the next role down. - * - * @param GroupId The group ID to demote in. - * @param UserIds The ids of the users to demote. - * @param Session The session of the user. - * @param Success Delegate called upon successfully demoting users in the group. - * @param Error Delegate called if an error occurs, detailing the failure. - * - */ - UFUNCTION(Category = "Nakama|Groups") - void DemoteGroupUsers( - UNakamaSession* Session, - const FString& GroupId, - const TArray& UserIds, - FOnDemoteGroupUsers Success, - FOnError Error - ); - - /** - * Delete a group by id. - * - * @param GroupId The group id to to remove. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting the group. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Groups") - void DeleteGroup( - UNakamaSession* Session, - const FString& GroupId, - FOnRemoveGroup Success, - FOnError Error - ); - - // --- Notifications --- // - - /** - * List notifications for the user with an optional cursor. - * - * @param Limit The number of notifications to list. - * @param Cursor A cursor for the current position in notifications to list. - * @param Session The session of the user. - * @param Success Delegate called upon successfully listing the notifications. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Notifications") - void ListNotifications( - UNakamaSession* Session, - int32 Limit, - const FString& Cursor, - FOnListNotifications Success, - FOnError Error - ); - - /** - * Delete one or more notifications by id. - * - * @param NotificationIds The notification ids to remove. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting the notifications. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Notifications") - void DeleteNotifications( - UNakamaSession* Session, - const TArray& NotificationIds, - FOnDeleteNotifications Success, - FOnError Error - ); - - // --- Storage --- // - - /** - * Write objects to the storage engine. - * - * @param StorageObjectsData The objects to write. - * @param Session The session of the user. - * @param Success Delegate called upon successfully writing storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Storage") - void WriteStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnStorageObjectAcks Success, - FOnError Error - ); - - /** - * Read one or more objects from the storage engine. - * - * @param StorageObjectsData The objects to read. - * @param Session The session of the user. - * @param Success Delegate called upon successfully reading the storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Storage") - void ReadStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnStorageObjectsRead Success, - FOnError Error - ); - - /** - * List storage objects in a collection which belong to a specific user and have public read access. - * - * @param Collection The collection to list over. - * @param UserId The user ID of the user to list objects for. - * @param Limit The number of objects to list. - * @param Cursor A cursor to paginate over the collection. - * @param Session The session of the user. - * @param Success Delegate called upon successfully listing the storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Storage") - void ListStorageObjects( - UNakamaSession* Session, - const FString& Collection, - const FString& UserId, - int32 Limit, - const FString& Cursor, - FOnStorageObjectsListed Success, - FOnError Error - ); - - /** - * [DEPRECATED] Delete one or more storage objects. - * - * @param StorageObjectsData The ids of the objects to delete. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Storage", meta=(DeprecatedFunction, DeprecationMessage="Use DeleteStorageObjects instead")) - void RemoveStorageObjects( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnRemovedStorageObjects Success, - FOnError Error - ); - - /** - * Delete one or more storage objects. - * - * @param StorageObjectsData The ids of the objects to delete. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Storage") - void DeleteStorageObjects ( - UNakamaSession* Session, - const TArray& StorageObjectsData, - FOnRemovedStorageObjects Success, - FOnError Error - ); - - /** - * List parties and optionally filter by matching criteria. - * - * @param Limit Limit the number of returned parties. - * @param Open Optionally filter by open/closed parties. - * @param Query Arbitrary label query. - * @param Cursor Cursor for the next page of results, if any. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting storage objects. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Parties") - void ListParties ( - UNakamaSession* Session, - int32 Limit, - bool Open, - const FString& Query, - const FString& Cursor, - FOnListedParties Success, - FOnError Error - ); - - // --- RPC --- // - - /** - * Send an RPC message to the server. - * - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Session The session of the user. - * @param Success Delegate called upon successfully sending the RPC message and receiving a response. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|RPC") - bool RPC( - UNakamaSession* Session, - const FString& FunctionId, - const FString& Payload, - FOnRPC Success, - FOnError Error - ); - - // --- RPC HttpKey --- // - - /** - * Send an RPC message to the server using HTTP key. - * - * @param HttpKey The HTTP key for the server. - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Success Delegate called upon successfully sending the RPC message using the HTTP key and receiving a response. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|RPC") - bool RPCHttpKey( - const FString& HttpKey, - const FString& FunctionId, - const FString& Payload, - FOnRPC Success, - FOnError Error - ); - - - // --- List Channel Messages --- // - - /** - * List messages from a chat channel. - * - * @param Session The session of the user. - * @param ChannelId A channel identifier. - * @param Limit The number of chat messages to list. - * @param Cursor A cursor for the current position in the messages history to list. - * @param Forward Fetch messages forward from the current cursor (or the start). - * @param Success Delegate called upon successfully retrieving the list of chat messages from the specified channel, returning the messages. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat") - void ListChannelMessages( - UNakamaSession* Session, - const FString& ChannelId, - int32 Limit, - const FString& Cursor, - bool Forward, - FOnListChannelMessages Success, - FOnError Error - ); - - // --- Leaderboards --- // - - /** - * Write a record to a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to write. - * @param Score The score for the leaderboard record. - * @param SubScore The subscore for the leaderboard record. - * @param Metadata The metadata for the leaderboard record. - * @param Session The session of the user. - * @param Success Delegate called upon successfully writing the record to the specified leaderboard, confirming the record's acceptance. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Leaderboards") - void WriteLeaderboardRecord( - UNakamaSession* Session, - const FString& LeaderboardId, - int64 Score, - int64 SubScore, - const FString& Metadata, - FOnWriteLeaderboardRecord Success, - FOnError Error - ); - - /** - * List records from a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerIds Record owners to fetch with the list of records. - * @param Limit The number of records to list. - * @param Cursor A cursor for the current position in the leaderboard records to list. - * @param ListBy List by either Score or Friends - * @param Session The session of the user. - * @param Success Delegate called upon successfully retrieving the list of leaderboard records, returning the specified records. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Leaderboards") - void ListLeaderboardRecords( - UNakamaSession* Session, - const FString& LeaderboardId, - const TArray& OwnerIds, - int32 Limit, - const FString& Cursor, - ENakamaLeaderboardListBy ListBy, - FOnListLeaderboardRecords Success, - FOnError Error - ); - - /** - * List leaderboard records from a given leaderboard around the owner. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param Success Delegate called upon successfully retrieving the leaderboard records centered around the specified owner, returning the surrounding records. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Leaderboards") - void ListLeaderboardRecordsAroundOwner( - UNakamaSession* Session, - const FString& LeaderboardId, - const FString& OwnerId, - int32 Limit, - FOnListLeaderboardRecords Success, - FOnError Error - ); - - /** - * Delete a leaderboard record. - * - * @param LeaderboardId The id of the leaderboard with the record to be deleted. - * @param Session The session of the user. - * @param Success Delegate called upon successfully deleting the specified leaderboard record, confirming the deletion. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Leaderboards") - void DeleteLeaderboardRecord( - UNakamaSession* Session, - const FString& LeaderboardId, - FOnDeletedLeaderboardRecord Success, - FOnError Error - ); - - // --- Tournaments --- // - - /** - * A request to submit a score to a tournament. - * - * @param TournamentId The tournament ID to write the record for. - * @param Score The score value to submit. - * @param SubScore An optional secondary value. - * @param Metadata A JSON object of additional properties. - * @param Session The session of the user. - * @param Success Delegate called upon successfully submitting the score to the specified tournament, confirming the submission. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Tournaments") - void WriteTournamentRecord( - UNakamaSession* Session, - const FString& TournamentId, - int64 Score, - int64 SubScore, - const FString& Metadata, - FOnWriteLeaderboardRecord Success, - FOnError Error - ); - - /** - * List tournament records from a given tournament. - * - * @param TournamentId The ID of the tournament to list for. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next or previous page cursor. - * @param OwnerIds One or more owners to retrieve records for. - * @param ListBy List By Score or Friends - * @param Session The session of the user. - * @param Success Delegate called with a list of tournament records from the specified tournament. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Tournaments") - void ListTournamentRecords( - UNakamaSession* Session, - const FString& TournamentId, - int32 Limit, - const FString& Cursor, - const TArray& OwnerIds, - ENakamaLeaderboardListBy ListBy, - FOnListTournamentRecords Success, - FOnError Error - ); - - /** - * List tournament records from a given tournament around the owner. - * - * @param TournamentId The ID of the tournament to list for. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param Success Delegate called with a list of tournament records centered around the specified owner. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Tournaments") - void ListTournamentRecordsAroundOwner( - UNakamaSession* Session, - const FString& TournamentId, - const FString& OwnerId, - int32 Limit, - FOnListTournamentRecords Success, - FOnError Error - ); - - /** - * Join a tournament if it has open membership or request to join it. - * - * @param TournamentId The id of the tournament to join. - * @param Session The session of the user. - * @param Success Delegate called upon successfully joining or requesting to join the specified tournament. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Tournaments") - void JoinTournament( - UNakamaSession* Session, - const FString& TournamentId, - FOnJoinedTournament Success, - FOnError Error - ); - - /** - * List active/upcoming tournaments based on given filters. - * - * @param CategoryStart The start of the categories to include. Defaults to 0. - * @param CategoryEnd The end of the categories to include. Defaults to 128. - * @param StartTime The start time for tournaments. Defaults to current Unix time. - * @param EndTime The end time for tournaments. Defaults to +1 year from current Unix time. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next page cursor for listings. - * @param Session The session of the user. - * @param Success Delegate called with a list of active/upcoming tournaments based on the given filters. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Tournaments") - void ListTournaments( - UNakamaSession* Session, - int32 CategoryStart, - int32 CategoryEnd, - int32 StartTime, - int32 EndTime, - int32 Limit, - const FString& Cursor, - FOnListTournaments Success, - FOnError Error - ); - - - // --- TFUNCTIONS (LAMBDAS) SECTIONS --- // - - // --- Authentication --- // - - /** - * Authenticate a user with a device id. - * - * @param DeviceId A device identifier usually obtained from a platform API. - * @param Username A username used to create the user. Defaults to empty string. - * @param bCreate True if the user should be created when authenticated. Defaults to false. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateDevice( - const FString& DeviceId, - const TOptional bCreate, - const TOptional& Username, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with an email and password. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateEmail( - const FString& Email, - const FString& Password, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with a custom id. - * - * @param CustomId A custom identifier usually obtained from an external authentication service. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateCustom( - const FString& CustomId, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Social Authentication --- // - - /** - * Authenticate a user with Apple Sign In. - * - * @param Token The ID token received from Apple to validate. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateApple( - const FString& Token, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with a Facebook auth token. - * - * @param Token An OAuth access token from the Facebook SDK. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param bImport True if the Facebook friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked when authentication succeeds, returning the user session. - * @param ErrorCallback Callback invoked if authentication fails, detailing the error. - */ - void AuthenticateFacebook(const FString& Token, - const FString& Username, - bool bCreate, - bool bImport, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with a Google auth token. - * - * @param Token An OAuth access token from the Google SDK. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateGoogle( - const FString& Token, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with Apple Game Center. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimestampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateGameCenter( - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - const FString& Username, - bool bCreate, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Authenticate a user with a Steam auth token. - * - * @param Token An authentication token from the Steam network. - * @param Username A username used to create the user. - * @param bCreate True if the user should be created when authenticated. - * @param bImport True if the Steam friends should be imported. - * @param Vars Extra information that will be bundled in the session token. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - */ - void AuthenticateSteam( - const FString& Token, - const FString& Username, - bool bCreate, - bool bImport, - const TMap& Vars, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Refresh a user's session using a refresh token retrieved from a previous authentication request. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when authentication succeeds, returning the user session. - * @param ErrorCallback Callback invoked if authentication fails, detailing the error. - * @param SuccessCallback Callback invoked upon successful authentication, returning the user's session. - * @param ErrorCallback Callback invoked when an error occurs during authentication, detailing the failure. - **/ - void AuthenticateRefresh( - UNakamaSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Linking --- // - - /** - * - * Link a device id to the user account owned by the session. - * @param Id A device identifier usually obtained from a platform API. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the device ID is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkDevice( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link an email with password to the user account owned by the session. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the email is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkEmail( - UNakamaSession* Session, - const FString& Email, - const FString& Password, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * - * Link a custom id to the user account owned by the session. - * @param Id A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the custom ID is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkCustom( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link an Apple ID to the social profiles on the current user's account. - * - * @param Token The ID token received from Apple. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Apple ID is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkApple( - UNakamaSession* Session, - const FString& Token, TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link a Facebook profile to a user account. - * - * @param Token An OAuth access token from the Facebook SDK. - * @param bImport True if the Facebook friends should be imported. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Facebook profile is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkFacebook( - UNakamaSession* Session, - const FString& Token, - TOptional bImport, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link a Google profile to a user account. - * - * @param Token An OAuth access token from the Google SDK. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Google profile is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkGoogle( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link a Game Center profile to a user account. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimestampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Game Center profile is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkGameCenter( - UNakamaSession* Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Link a Steam profile to a user account. - * - * @param Token An authentication token from the Steam network. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Steam profile is successfully linked. - * @param ErrorCallback Callback invoked if the linking process fails, detailing the error. - */ - void LinkSteam( - UNakamaSession* Session, - const FString& Token, - //bool bImport, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Unlinking --- // - - /** - * Unlink a device id from the user account owned by the session. - * - * @param Id A device identifier usually obtained from a platform API. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the device ID is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkDevice( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink an email with password from the user account owned by the session. - * - * @param Email The email address of the user. - * @param Password The password for the user. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the email is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkEmail( - UNakamaSession* Session, - const FString& Email, - const FString& Password, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a custom id from the user account owned by the session. - * - * @param Id A custom identifier usually obtained from an external authentication service. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the custom ID is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkCustom( - UNakamaSession* Session, - const FString& Id, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a Apple profile from the user account owned by the session. - * - * @param Token An Apple authentication token. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Apple profile is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkApple( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a Facebook profile from the user account owned by the session. - * - * @param Token An OAuth access token from the Facebook SDK. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Facebook profile is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkFacebook( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a Google profile from the user account owned by the session. - * - * @param Token An OAuth access token from the Google SDK. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Google profile is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkGoogle( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a Game Center profile from the user account owned by the session. - * - * @param PlayerId The player id of the user in Game Center. - * @param BundleId The bundle id of the Game Center application. - * @param TimestampSeconds The date and time that the signature was created. - * @param Salt A random NSString used to compute the hash and keep it randomized. - * @param Signature The verification signature data generated. - * @param PublicKeyUrl The URL for the public encryption key. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Game Center profile is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkGameCenter( - UNakamaSession* Session, - const FString& PlayerId, - const FString& BundleId, - int64 TimestampSeconds, - const FString& Salt, - const FString& Signature, - const FString& PublicKeyUrl, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unlink a Steam profile from the user account owned by the session. - * - * @param Token An authentication token from the Steam network. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked when the Steam profile is successfully unlinked. - * @param ErrorCallback Callback invoked if the unlinking process fails, detailing the error. - */ - void UnLinkSteam( - UNakamaSession* Session, - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Import Friends --- // - - /** - * Import Facebook friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Facebook. This function can be used to be - * explicit with the import operation. - * - * @param Session The session of the user. - * @param Token An OAuth access token from the Facebook SDK. - * @param bReset True if the Facebook friend import for the user should be reset. - * @param SuccessCallback Callback invoked upon successful import of Facebook friends. - * @param ErrorCallback Callback invoked if the Facebook friends import operation fails, providing error details. - */ - void ImportFacebookFriends( - UNakamaSession* Session, - const FString& Token, - const TOptional bReset, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Import Steam friends and add them to the user's account. - * - * The server will import friends when the user authenticates with Steam. This function can be used to be - * explicit with the import operation. - * - * @param Session The session of the user. - * @param SteamToken The Steam token to use. - * @param bReset True if the Steam friend import for the user should be reset. - * @param SuccessCallback Callback invoked upon successful import of Steam friends. - * @param ErrorCallback Callback invoked if the Steam friends import operation fails, providing error details. - */ - void ImportSteamFriends( - UNakamaSession* Session, - const FString& SteamToken, - const TOptional bReset, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Accounts --- // - - /** - * - * Fetch the user account owned by the session. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully retrieving user account details. - * @param ErrorCallback Callback invoked if the fetch operation for user account details fails, providing error information. - */ - void GetAccount( - UNakamaSession *Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Update the current user's account on the server. - * - * @param Username The new username for the user. - * @param DisplayName A new display name for the user. - * @param AvatarUrl A new avatar url for the user. - * @param LangTag A new language tag in BCP-47 format for the user. - * @param Location A new location for the user. - * @param TimeZone New timezone information for the user. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully updating the user's account details. - * @param ErrorCallback Callback invoked if the update operation fails, detailing the encountered error. - */ - void UpdateAccount( - UNakamaSession *Session, - const TOptional& Username, - const TOptional& DisplayName, - const TOptional& AvatarUrl, - const TOptional& LangTag, - const TOptional& Location, - const TOptional& TimeZone, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - - /** - * Delete the current user from the server. - * - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting the user's account. - * @param ErrorCallback Callback invoked if the delete operation fails, detailing the encountered error. - */ - void DeleteUser(UNakamaSession* Session, TFunction SuccessCallback, - TFunction ErrorCallback); - - // --- Users --- // - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param UserIds List of user IDs. - * @param Usernames List of usernames. - * @param FacebookIds List of Facebook IDs. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully retrieving the users' details. - * @param ErrorCallback Callback invoked if the user fetch operation fails, providing error information. - */ - void GetUsers( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - const TArray& FacebookIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Friends --- // - - /** - * Add one or more friends by id. - * - * @param UserIds The ids of the users to add or invite as friends. - * @param Usernames The usernames of the users to add as friends. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successful friend addition. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void AddFriends( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Delete one more or users by id or username from friends. - * - * @param UserIds the user ids to remove as friends. - * @param Usernames The usernames to remove as friends. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successful friend removal. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - * - */ - void DeleteFriends( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Block one or more friends by id. - * - * @param UserIds The ids of the users to block. - * @param Usernames The usernames of the users to block. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully blocking friends. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void BlockFriends( - UNakamaSession *Session, - const TArray& UserIds, - const TArray& Usernames, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List of friends of the current user. - * - * @param Limit The max number of records to return. Between 1 and 1000. - * @param State The friend state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successful fetch, returning the list of friends. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListFriends( - UNakamaSession *Session, - const TOptional& Limit, - TOptional State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Groups --- // - - /** - * Create a group. - * - * @param Name The name for the group. - * @param Description A description for the group. - * @param AvatarUrl An avatar url for the group. - * @param LangTag A language tag in BCP-47 format for the group. - * @param bOpen True if the group should have open membership. - * @param MaxCount Maximum number of group members. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully creating the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void CreateGroup( - UNakamaSession *Session, - const FString& Name, - const FString& Description, - const FString& AvatarUrl, - const FString& LangTag, - const bool bOpen, - const TOptional& MaxCount, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Delete a group by id. - * - * @param GroupId The group id to to remove. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void DeleteGroup( - UNakamaSession *Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Add one or more users to the group. - * - * @param GroupId The id of the group to add users into. - * @param UserIds The ids of the users to add or invite to the group. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully adding users to the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void AddGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List all users part of the group. - * - * @param GroupId The id of the group. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The group state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the group members. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TOptional& Limit, - TOptional State, - FString Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Kick one or more users from the group. - * - * @param GroupId The id of the group. - * @param UserIds The ids of the users to kick. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully kicking users from the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void KickGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - - /** - * Ban one or more users from the group. - * - * @param GroupId The id of the group. - * @param UserIds The ids of the users to ban. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully banning users from the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void BanGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join a group if it has open membership or request to join it. - * - * @param GroupId The id of the group to join. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully joining the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinGroup( - UNakamaSession *Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Leave a group by id. - * - * @param GroupId The id of the group to leave. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully leaving the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void LeaveGroup( - UNakamaSession *Session, - const FString& GroupId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List groups on the server. - * - * @param Name The name filter to apply to the group list. - * @param Limit The number of groups to list. - * @param Cursor A cursor for the current position in the groups to list. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the groups. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListGroups( - UNakamaSession *Session, - const FString& Name, - int32 Limit, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List of groups the current user (from Session) is a member of. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The group state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the user's groups. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListUserGroups( - UNakamaSession *Session, - const TOptional& Limit, - const TOptional& State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); // Uses UserId from Session - - /** - * List of groups the 'UserId' is a member of. - * @param UserId The id of the user whose groups to list. - * @param Limit The max number of records to return. Between 1 and 100. - * @param State The group state to list. - * @param Cursor An optional next page cursor. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the user's groups. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListUserGroups( - UNakamaSession *Session, - const FString& UserId, - const TOptional& Limit, - const TOptional& State, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); // Has UserId - - /** - * Promote a set of users in a group to the next role up. - * - * @param GroupId The group ID to promote in. - * @param UserIds The ids of the users to promote. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully promoting users in the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void PromoteGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Demote a set of users in a group to the next role down. - * - * @param GroupId The group ID to demote in. - * @param UserIds The ids of the users to demote. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully demoting users in the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void DemoteGroupUsers( - UNakamaSession *Session, - const FString& GroupId, - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Update a group. - * - * The user must have the correct access permissions for the group. - * - * @param GroupId The id of the group to update. - * @param Name A new name for the group. - * @param Description A new description for the group. - * @param AvatarUrl A new avatar url for the group. - * @param LangTag A new language tag in BCP-47 format for the group. - * @param bOpen True if the group should have open membership. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully demoting users in the group. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void UpdateGroup( - UNakamaSession *Session, - const FString& GroupId, - const TOptional& Name, - const TOptional& Description, - const TOptional& AvatarUrl, - const TOptional& LangTag, - const TOptional bOpen, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Leaderboards --- // - - /** - * List records from a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerIds Record owners to fetch with the list of records. - * @param Limit The number of records to list. - * @param Cursor A cursor for the current position in the leaderboard records to list. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully retrieving the list of leaderboard records, returning the specified records. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListLeaderboardRecords( - UNakamaSession *Session, - const FString& LeaderboardId, - const TArray& OwnerIds, - const TOptional& Limit, - const TOptional& Cursor, - TFunction - SuccessCallback, TFunction ErrorCallback - ); - - /** - * List leaderboard records from a given leaderboard around the owner. - * - * @param LeaderboardId The id of the leaderboard to list. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully retrieving the leaderboard records centered around the specified owner, returning the surrounding records. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListLeaderboardRecordsAroundOwner( - UNakamaSession *Session, - const FString& LeaderboardId, - const FString& OwnerId, - const TOptional& Limit, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Write a record to a leaderboard. - * - * @param LeaderboardId The id of the leaderboard to write. - * @param Score The score for the leaderboard record. - * @param Subscore The subscore for the leaderboard record. - * @param Metadata The metadata for the leaderboard record. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully writing the record to the specified leaderboard, confirming the record's acceptance. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void WriteLeaderboardRecord( - UNakamaSession *Session, - const FString& LeaderboardId, - int64 Score, - const TOptional& Subscore, - const TOptional& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Delete a leaderboard record. - * - * @param LeaderboardId The id of the leaderboard with the record to be deleted. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting the specified leaderboard record, confirming the deletion. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void DeleteLeaderboardRecord( - UNakamaSession *Session, - const FString& LeaderboardId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Matches --- // - - /** - * Fetch a list of matches active on the server. - * - * @param Session The session of the user. - * @param MinSize The minimum number of match participants. - * @param MaxSize The maximum number of match participants. - * @param Limit The number of matches to list. - * @param Label The label to filter the match list on. - * @param Query The query to the match listing. - * @param Authoritative True to include authoritative matches. - * @param SuccessCallback Callback invoked when the matches are fetched successfully, returning the match list. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListMatches( - UNakamaSession *Session, - const TOptional& MinSize, - const TOptional& MaxSize, - const TOptional& Limit, - const TOptional& Label, - const TOptional& Query, - const TOptional Authoritative, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Notifications --- // - - /** - * List notifications for the user with an optional cursor. - * - * @param Limit The number of notifications to list. - * @param CacheableCursor A cursor for the current position in notifications to list. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the notifications. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListNotifications( - UNakamaSession *Session, - const TOptional& Limit, - const TOptional& CacheableCursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Delete one or more notifications by id. - * - * @param NotificationIds The notification ids to remove. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting the notifications. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void DeleteNotifications( - UNakamaSession *Session, - const TArray& NotificationIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Chat --- // - - /** - * List messages from a chat channel. - * - * @param Session The session of the user. - * @param ChannelId A channel identifier. - * @param Limit The number of chat messages to list. - * @param Cursor A cursor for the current position in the messages history to list. - * @param Forward Fetch messages forward from the current cursor (or the start). - * @param SuccessCallback Callback invoked upon successfully retrieving the list of chat messages from the specified channel, returning the messages. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListChannelMessages( - UNakamaSession *Session, - const FString& ChannelId, - const TOptional& Limit, - const TOptional& Cursor, - const TOptional Forward, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Tournaments --- // - - /** - * List active/upcoming tournaments based on given filters. - * - * @param CategoryStart The start of the categories to include. Defaults to 0. - * @param CategoryEnd The end of the categories to include. Defaults to 128. - * @param StartTime The start time for tournaments. Defaults to current Unix time. - * @param EndTime The end time for tournaments. Defaults to +1 year from current Unix time. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next page cursor for listings. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked with a list of active/upcoming tournaments based on the given filters. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListTournaments( - UNakamaSession *Session, - const TOptional& CategoryStart, - const TOptional& CategoryEnd, - const TOptional& StartTime, - const TOptional& EndTime, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List tournament records from a given tournament. - * - * @param TournamentId The ID of the tournament to list for. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Cursor A next or previous page cursor. - * @param OwnerIds One or more owners to retrieve records for. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked with a list of tournament records from the specified tournament. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListTournamentRecords( - UNakamaSession *Session, - const FString& TournamentId, - const TOptional& Limit, - const TOptional& Cursor, - const TArray& OwnerIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List tournament records from a given tournament around the owner. - * - * @param TournamentId The ID of the tournament to list for. - * @param OwnerId The owner to retrieve records around. - * @param Limit Max number of records to return. Between 1 and 100. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked with a list of tournament records centered around the specified owner. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListTournamentRecordsAroundOwner( - UNakamaSession *Session, - const FString& TournamentId, - const FString& OwnerId, - const TOptional& Limit, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * A request to submit a score to a tournament. - * - * @param TournamentId The tournament ID to write the record for. - * @param Score The score value to submit. - * @param Subscore An optional secondary value. - * @param Metadata A JSON object of additional properties. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully submitting the score to the specified tournament, confirming the submission. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void WriteTournamentRecord( - UNakamaSession *Session, - const FString& TournamentId, - int64 Score, - const TOptional& Subscore, - const TOptional& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join a tournament if it has open membership or request to join it. - * - * @param TournamentId The id of the tournament to join. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully joining or requesting to join the specified tournament. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinTournament( - UNakamaSession *Session, - const FString& TournamentId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Storage --- // - - /** - * List storage objects in a collection which belong to current user. - * - * @param Collection The collection to list over. - * @param Limit The number of objects to list. - * @param Cursor A cursor to paginate over the collection. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListStorageObjects( - UNakamaSession *Session, - const FString& Collection, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List storage objects in a collection which belong to a specific user and have public read access. - * - * @param Collection The collection to list over. - * @param UserId The user ID of the user to list objects for. - * @param Limit The number of objects to list. - * @param Cursor A cursor to paginate over the collection. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully listing the storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListUsersStorageObjects( - UNakamaSession *Session, - const FString& Collection, - const FString& UserId, - const TOptional& Limit, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Write objects to the storage engine. - * - * @param Objects The objects to write. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully writing storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void WriteStorageObjects( - UNakamaSession *Session, - const TArray& Objects, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Read one or more objects from the storage engine. - * - * @param ObjectIds The objects to read. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully reading the storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ReadStorageObjects( - UNakamaSession *Session, - const TArray& ObjectIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Delete one or more storage objects. - * - * @param ObjectIds The ids of the objects to delete. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void DeleteStorageObjects( - UNakamaSession *Session, - const TArray& ObjectIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * List parties and optionally filter by matching criteria. - * - * @param Limit Limit the number of returned parties. - * @param Open Optionally filter by open/closed parties. - * @param Query Arbitrary label query. - * @param Cursor Cursor for the next page of results, if any. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully deleting storage objects. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListParties ( - UNakamaSession* Session, - const TOptional& Limit, - const TOptional& Open, - const TOptional& Query, - const TOptional& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- RPC --- // - - /** - * Send an RPC message to the server. - * returns true if the call was made. - * - * @param Id The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully sending the RPC message and receiving a response. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - bool RPC ( - UNakamaSession *Session, - const FString& Id, - const TOptional& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Send an RPC message to the server using HTTP key. - * returns true if the call was made. - * - * @param HttpKey The HTTP key for the server. - * @param Id The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param SuccessCallback Callback invoked upon successfully sending the RPC message using the HTTP key and receiving a response. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - bool RPC ( // HTTPKey - const FString& HttpKey, - const FString& Id, - const FString& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Send an RPC message to the server. - * returns true if the call was made. - * - * @param Id The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Session The session of the user. - * @param SuccessCallback Callback invoked upon successfully sending the RPC message and receiving a response. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - bool RPCm ( - UNakamaSession *Session, - const FString& Id, - TOptional&& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Send an RPC message to the server using HTTP key. - * returns true if the call was made. - * - * @param HttpKey The HTTP key for the server. - * @param Id The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param SuccessCallback Callback invoked upon successfully sending the RPC message using the HTTP key and receiving a response. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - bool RPCm ( // HTTPKey - const FString& HttpKey, - const FString& Id, - FString&& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - -private: - - // Utils - FString ConstructURL(const FString& Endpoint); - - // Make HTTP request - TSharedRef MakeRequest( - const FString& Endpoint, - const FString& Content, - ENakamaRequestMethod RequestMethod, - const TMultiMap& QueryParams, - const FString& SessionToken - ); - - // Working with requests - bool IsClientValid() const; - - // Requests for RPC - bool SendRPC( - UNakamaSession* Session, - const FString& Id, - const TOptional& Payload, - TMultiMap QueryParams, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - bool SendRPCm( - UNakamaSession* Session, - const FString& Id, - const TOptional& Payload, - TMultiMap QueryParams, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // Requests - TArray ActiveRequests; - FCriticalSection ActiveRequestsMutex; - -}; - - - - - - diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaError.h b/Nakama/Source/NakamaUnreal/Public/NakamaError.h deleted file mode 100644 index c1a57f26b..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaError.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaError.generated.h" - -// Error Data -UENUM(BlueprintType) -enum class ENakamaErrorCode : uint8 -{ - Ok = 0 UMETA(DisplayName = "Ok"), - Cancelled = 1 UMETA(DisplayName = "Cancelled"), - Unknown = 2 UMETA(DisplayName = "Unknown"), - InvalidArgument = 3 UMETA(DisplayName = "Invalid Argument"), - DeadlineExceeded = 4 UMETA(DisplayName = "Deadline Exceeded"), - NotFound = 5 UMETA(DisplayName = "Not Found"), - AlreadyExists = 6 UMETA(DisplayName = "Already Exists"), - PermissionDenied = 7 UMETA(DisplayName = "Permission Denied"), - ResourceExhausted = 8 UMETA(DisplayName = "Resource Exhausted"), - FailedPrecondition = 9 UMETA(DisplayName = "Failed Precondition"), - Aborted = 10 UMETA(DisplayName = "Aborted"), - OutOfRange = 11 UMETA(DisplayName = "Out Of Range"), - Unimplemented = 12 UMETA(DisplayName = "Unimplemented"), - Internal = 13 UMETA(DisplayName = "Internal"), - Unavailable = 14 UMETA(DisplayName = "Unavailable"), - DataLoss = 15 UMETA(DisplayName = "Data Loss"), - Unauthenticated = 16 UMETA(DisplayName = "Unauthenticated") -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaError -{ - GENERATED_BODY() - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Error") - FString Message; - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama Error") - ENakamaErrorCode Code; - - FNakamaError(const FString& JsonString); - FNakamaError(): Code(ENakamaErrorCode::Unknown) { } - - ENakamaErrorCode ConvertNakamaErrorCode(int32 CodeValue); - - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaFriend.h b/Nakama/Source/NakamaUnreal/Public/NakamaFriend.h deleted file mode 100644 index 130037b9d..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaFriend.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaUser.h" -#include "NakamaFriend.generated.h" - -// The friendship status. -UENUM(BlueprintType) -enum class ENakamaFriendState : uint8 -{ - FRIEND UMETA(DisplayName = "Friend"), // The user is a friend of the current user. - INVITE_SENT UMETA(DisplayName = "Invite Sent"), // The current user has sent an invite to the user. - INVITE_RECEIVED UMETA(DisplayName = "Invite Received"), // The current user has received an invite from this user. - BLOCKED UMETA(DisplayName = "Blocked"), // The current user has blocked this user. - ALL UMETA(DisplayName = "All"), // Custom for this Plugin -}; - - -// A friend of a user. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaFriend -{ - GENERATED_BODY() - - // The user object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - FNakamaUser NakamaUser; - - // The friend status. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - ENakamaFriendState UserState; - - // Time of the latest relationship update. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FDateTime UpdateTime; - - FNakamaFriend(const FString& JsonString); - FNakamaFriend(const TSharedPtr JsonObject); - FNakamaFriend(); - - static ENakamaFriendState GetFriendStateFromString(const FString& StateString); -}; - -USTRUCT(BlueprintType) // Internal Unreal Class (No Need to Convert) -struct NAKAMAUNREAL_API FNakamaFriendChat -{ - GENERATED_BODY() - - // User - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - FNakamaUser NakamaUser; - - // Chat Id. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - FString ChatId; - -}; - -// A collection of zero or more friends of the user. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaFriendList -{ - GENERATED_BODY() - - // The Friend objects. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - TArray NakamaUsers; - - // Cursor for the next page of results, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Friend") - FString Cursor; - - FNakamaFriendList(const FString& JsonString); - FNakamaFriendList(); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaGroup.h b/Nakama/Source/NakamaUnreal/Public/NakamaGroup.h deleted file mode 100644 index dbbff4126..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaGroup.h +++ /dev/null @@ -1,199 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaUser.h" -#include "NakamaGroup.generated.h" - -// The group role status. -UENUM(BlueprintType) -enum class ENakamaUserGroupState : uint8 -{ - SUPERADMIN UMETA(DisplayName = "Superadmin"), // The user is a superadmin with full control of the group. - ADMIN UMETA(DisplayName = "Admin"), // The user is an admin with additional privileges. - MEMBER UMETA(DisplayName = "Member"), // The user is a regular member. - JOIN_REQUEST UMETA(DisplayName = "Join Request"), // The user has requested to join the group - ALL UMETA(DisplayName = "All"), // All group states -}; - -static ENakamaGroupState GetGroupStateFromString(const FString& StateString); - - -// A group in the server. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaGroup -{ - GENERATED_BODY() - - // The id of a group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Id; - - // The id of the user who created the group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString CreatorId; - - // The id of the user who created the group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Name; - - // A description for the group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Description; - - // The language expected to be a tag which follows the BCP-47 spec. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Language; - - // Additional information stored as a JSON object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString MetaData; - - // A URL for an avatar image. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString AvatarUrl; - - // Anyone can join open groups, otherwise only admins can accept members. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - bool open = false; - - // The current count of all members in the group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - int32 EdgeCount = 0; - - // The maximum number of members allowed. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - int32 MaxCount = 0; - - // The UNIX time when the group was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FDateTime CreateTime = 0; - // The UNIX time when the group was last updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FDateTime UpdateTime = 0; - - FNakamaGroup(const FString& JsonString); - FNakamaGroup(const TSharedPtr JsonObject); - FNakamaGroup() { } -}; - -// Group States -UENUM(BlueprintType) -enum class ENakamaGroupState : uint8 -{ - SUPERADMIN UMETA(DisplayName = "Superadmin"), - ADMIN UMETA(DisplayName = "Admin"), - MEMBER UMETA(DisplayName = "Member"), - JOIN_REQUEST UMETA(DisplayName = "Join Request"), - ALL UMETA(DisplayName = "All"), -}; - -// Group User -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaGroupUser -{ - GENERATED_BODY() - - // User. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FNakamaUser User; - - // Their relationship to the group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - ENakamaGroupState State; - - FNakamaGroupUser(const FString& JsonString); - FNakamaGroupUser(const TSharedPtr JsonObject); - FNakamaGroupUser(); -}; - -// Group Users list (Members) -// A list of users belonging to a group, along with their role. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaGroupUsersList -{ - GENERATED_BODY() - - // User-role pairs for a group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - TArray GroupUsers; - - // Cursor for the next page of results, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Cursor; - - FNakamaGroupUsersList(const FString& JsonString); - FNakamaGroupUsersList() { } - -}; - -// One or more groups returned from a listing operation. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaGroupList -{ - GENERATED_BODY() - - // One or more groups. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - TArray Groups; - - // A cursor used to get the next page. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Cursor; - - FNakamaGroupList(const FString& JsonString); - FNakamaGroupList() { } -}; - -// Owned by a player (I am member of..) -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUserGroup -{ - GENERATED_BODY() - - // Group. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama|Groups") //VisibleAnywhere, BlueprintReadOnly - FNakamaGroup Group; - - // The user's relationship to the group. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama|Groups") //BlueprintReadOnly - ENakamaGroupState State; - - FNakamaUserGroup(const FString& JsonString); - FNakamaUserGroup(const TSharedPtr JsonObject); - FNakamaUserGroup(); -}; - -// A list of groups belonging to a user, along with the user's role in each group. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUserGroupList -{ - GENERATED_BODY() - - // Group-role pairs for a user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - TArray UserGroups; - - // Cursor for the next page of results, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Groups") - FString Cursor; - - FNakamaUserGroupList(const FString& JsonString); - FNakamaUserGroupList(); - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaLeaderboard.h b/Nakama/Source/NakamaUnreal/Public/NakamaLeaderboard.h deleted file mode 100644 index 4c846a777..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaLeaderboard.h +++ /dev/null @@ -1,133 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaLeaderboard.generated.h" - -// Leaderboardss -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaLeaderboardRecord -{ - GENERATED_BODY() - - // The ID of the leaderboard this score belongs to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString LeaderboardId; - - // The ID of the score owner, usually a user or group. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString OwnerId; - - // The username of the score owner, if the owner is a user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString Username; - - // The score value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - int64 Score = 0; - - // An optional subscore value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - int64 SubScore = 0; - - // The number of submissions to this score record. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - int64 NumScore = 0; - - // The maximum number of score updates allowed by the owner. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - int32 MaxNumScore = 0; - - // Metadata. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString Metadata; - - // The UNIX time when the leaderboard record was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FDateTime CreateTime = 0; - - //The UNIX time when the leaderboard record was updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FDateTime UpdateTime = 0; - - //The UNIX time when the leaderboard record expires. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FDateTime ExpiryTime = 0; - - //The rank of this record. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - int64 Rank = 0; - - FNakamaLeaderboardRecord(const FString& JsonString); - FNakamaLeaderboardRecord(const TSharedPtr JsonObject); - FNakamaLeaderboardRecord(); - -}; - - -UENUM(BlueprintType) -enum class ENakamaLeaderboardListBy : uint8 -{ - // The object is only writable by server runtime - BY_SCORE UMETA(DisplayName = "By Score"), - // Only the user who owns it may write - BY_FRIENDS UMETA(DisplayName = "By Friends"), -}; - - -UENUM(BlueprintType) -enum class ENakamaLeaderboardOperatorOverride : uint8 -{ - // Do not override the leaderboard operator - NO_OVERRIDE UMETA(DisplayName = "NO_OVERRIDE"), - // Override the leaderboard operator with BEST - BEST UMETA(DisplayName = "BEST"), - // Override the leaderboard operator with SET - SET UMETA(DisplayName = "SET"), - // Override the leaderboard operator with INCREMENT - INCREMENT UMETA(DisplayName = "INCREMENT"), - // Override the leaderboard operator with DECREMENT - DECREMENT UMETA(DisplayName = "DECREMENT"), -}; - - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaLeaderboardRecordList -{ - GENERATED_BODY() - - //A list of leaderboard records. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - TArray Records; - - //A batched set of leaderboard records belonging to specified owners. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - TArray OwnerRecords; - - //The cursor to send when retrieving the next page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString NextCursor; - - //The cursor to send when retrieving the previous page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Leaderboards") - FString PrevCursor; - - FNakamaLeaderboardRecordList(const FString& JsonString); - FNakamaLeaderboardRecordList(); - -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaLogger.h b/Nakama/Source/NakamaUnreal/Public/NakamaLogger.h deleted file mode 100644 index 237418f76..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaLogger.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "NakamaLogger.generated.h" - -//DECLARE_LOG_CATEGORY_EXTERN(LogNakama, Log, All); -DECLARE_LOG_CATEGORY_EXTERN(LogNakamaUnreal, Log, All); - -UENUM(BlueprintType, Category = "Nakama") -enum class ENakamaLogLevel : uint8 -{ - Debug, - Info, - Warn, - Error, - Fatal -}; - -/** - * - */ -UCLASS() -class NAKAMAUNREAL_API UNakamaLogger : public UObject -{ - GENERATED_BODY() - -public: - UNakamaLogger(); - - UFUNCTION(BlueprintCallable, Category = "Nakama") - static void SetLogLevel(ENakamaLogLevel InLogLevel); - - UFUNCTION(BlueprintCallable, Category = "Nakama") - static void Log(ENakamaLogLevel InLogLevel, const FString& Message); - - UFUNCTION(BlueprintCallable, Category = "Nakama") - static void EnableLogging(bool bEnable); - -private: - static ENakamaLogLevel CurrentLogLevel; - static bool bLoggingEnabled; - static bool IsLoggable(ENakamaLogLevel InLogLevel); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaLoggingMacros.h b/Nakama/Source/NakamaUnreal/Public/NakamaLoggingMacros.h deleted file mode 100644 index a2c689913..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaLoggingMacros.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -// Toggle logging on/off by defining NAKAMA_LOGS_ENABLED -#define NAKAMA_LOGS_ENABLED - -#ifdef NAKAMA_LOGS_ENABLED - - #define NAKAMA_LOG_DEBUG(Message) \ - UNakamaLogger::Log(ENakamaLogLevel::Debug, Message) - - #define NAKAMA_LOG_INFO(Message) \ - UNakamaLogger::Log(ENakamaLogLevel::Info, Message) - - #define NAKAMA_LOG_WARN(Message) \ - UNakamaLogger::Log(ENakamaLogLevel::Warn, Message) - - #define NAKAMA_LOG_ERROR(Message) \ - UNakamaLogger::Log(ENakamaLogLevel::Error, Message) - - #define NAKAMA_LOG_FATAL(Message) \ - UNakamaLogger::Log(ENakamaLogLevel::Fatal, Message) - -#else - - // Define empty macros if logging is disabled - #define NAKAMA_LOG_DEBUG(Message) - #define NAKAMA_LOG_INFO(Message) - #define NAKAMA_LOG_WARN(Message) - #define NAKAMA_LOG_ERROR(Message) - #define NAKAMA_LOG_FATAL(Message) - -#endif // NAKAMA_LOGS_ENABLED \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaMatch.h b/Nakama/Source/NakamaUnreal/Public/NakamaMatch.h deleted file mode 100644 index 5b4bf48e2..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaMatch.h +++ /dev/null @@ -1,95 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" -#include "NakamaMatch.generated.h" - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatch -{ - GENERATED_BODY() // NMatchmakerMatched - - // The ID of the match, can be used to join. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FString MatchId; - - // True if it's an server-managed authoritative match, false otherwise. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - bool Authoritative; - - // Match label, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FString Label; - - // Current number of users in the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - int32 Size = 0; - - // The users currently in the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - TArray Pressences; // Typo - - // A reference to the current user's presence in the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, DisplayName = "Self", Category = "Nakama|Realtime") - FNakamaUserPresence Me; - - FNakamaMatch(const FString& JsonString); - FNakamaMatch(const TSharedPtr JsonObject); - FNakamaMatch() : Authoritative(false), Size(0) { } - -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchData -{ - GENERATED_BODY() - - // The match unique ID. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FString MatchId; - - // A reference to the user presence that sent this data, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FNakamaUserPresence Presence; - - // User presences that have just left the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - int64 OpCode; - - // Data payload, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FString Data; - - FNakamaMatchData(const FString& JsonString); - FNakamaMatchData(); -}; - -// A list of realtime matches. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchList -{ - GENERATED_BODY() - - // A number of matches corresponding to a list operation. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - TArray Matches; - - FNakamaMatchList(const FString& JsonString); - FNakamaMatchList(); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaMatchTypes.h b/Nakama/Source/NakamaUnreal/Public/NakamaMatchTypes.h deleted file mode 100644 index fa7897763..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaMatchTypes.h +++ /dev/null @@ -1,109 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" -#include "NakamaMatchTypes.generated.h" - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchmakerUser -{ - GENERATED_BODY() - - // User info. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - FNakamaUserPresence Presence; - - // String properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - TMap StringProperties; - - // Numeric Properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - TMap NumericProperties; - - FNakamaMatchmakerUser(const FString& JsonString); - FNakamaMatchmakerUser(const TSharedPtr JsonObject); - FNakamaMatchmakerUser(); -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchmakerMatched -{ - GENERATED_BODY() - - // The matchmaking ticket that has completed. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - FString Ticket; - - // The match token or match ID to join. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - FString MatchId; - - // Match join token. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - FString Token; - - // The users that have been matched together, and information about their matchmaking data. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - TArray Users; - - // A reference to the current user and their properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Matchmaker") - FNakamaMatchmakerUser Me; - - FNakamaMatchmakerMatched(const FString& JsonString); - FNakamaMatchmakerMatched(); - -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchPresenceEvent -{ - GENERATED_BODY() //NMatchmakerMatched - - //The match unique ID. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - FString MatchId; - - //User presences that have just joined the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - TArray Joins; - - //User presences that have just left the match. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Realtime") - TArray Leaves; - - FNakamaMatchPresenceEvent(const FString& JsonString); - FNakamaMatchPresenceEvent(); -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaMatchmakerTicket -{ - GENERATED_BODY() - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama|Matchmaker") - FString TicketId; - - // Might want more properties here later. - - FNakamaMatchmakerTicket(const FString& JsonString); - FNakamaMatchmakerTicket(); - -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaNotification.h b/Nakama/Source/NakamaUnreal/Public/NakamaNotification.h deleted file mode 100644 index 46912010c..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaNotification.h +++ /dev/null @@ -1,78 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaNotification.generated.h" - -// A notification in the server. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaNotification -{ - GENERATED_BODY() - - // ID of the Notification. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FString Id; - - // Subject of the notification. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FString Subject; - - // Content of the notification in JSON. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FString Content; - - // Category code for this notification. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - int32 Code = 0; - - // ID of the sender, if a user. Otherwise 'null'. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FString SenderId; - - // The UNIX time when the notification was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FDateTime CreateTime = 0; - - // True if this notification was persisted to the database. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - bool Persistent = false; - - FNakamaNotification(const FString& JsonString); - FNakamaNotification(const TSharedPtr JsonObject); - FNakamaNotification(); -}; - -// A collection of zero or more notifications. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaNotificationList -{ - GENERATED_BODY() - - // Collection of notifications - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - TArray Notifications; - - // Use this cursor to paginate notifications. Cache this to catch up to new notifications. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Notification") - FString CacheableCursor; - - FNakamaNotificationList(const FString& JsonString); - FNakamaNotificationList(); - -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaParty.h b/Nakama/Source/NakamaUnreal/Public/NakamaParty.h deleted file mode 100644 index 943a6fde2..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaParty.h +++ /dev/null @@ -1,201 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" -#include "NakamaParty.generated.h" - - -// Parties -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaParty -{ - GENERATED_BODY() - - // The unique party identifier. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // True if the party is open to join. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - bool Open; - - // True if the party is hidden from public listings and searches. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - bool Hidden; - - // The maximum number of party members. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - int32 MaxSize; - - // The party label, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString Label; - - // The current user in this party. i.e. Yourself. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, DisplayName = "Self", Category = "Nakama|Parties") - FNakamaUserPresence Me; - - // The current party leader. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FNakamaUserPresence Leader; - - // All members currently in the party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - TArray Presences; - - FNakamaParty(const FString& JsonString); - FNakamaParty(const TSharedPtr JsonObject); - FNakamaParty(); // Default Constructor -}; - -// Incoming notification for one or more new presences attempting to join the party. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyJoinRequest -{ - GENERATED_BODY() - - // The ID of the party to get a list of join requests for. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // Presences attempting to join, or who have joined. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - TArray Presences; - - FNakamaPartyJoinRequest(const FString& JsonString); - FNakamaPartyJoinRequest(); // Default Constructor -}; - -// Incoming notification for one or more new presences attempting to join the party. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyMatchmakerTicket -{ - GENERATED_BODY() - - // The ID of the party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // The ticket that can be used to cancel matchmaking. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString Ticket; - - FNakamaPartyMatchmakerTicket(const FString& JsonString); - FNakamaPartyMatchmakerTicket(); -}; - -// Information about a party close event. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyClose -{ - GENERATED_BODY() - - // The unique party identifier of the closing party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString Id; - - FNakamaPartyClose(const FString& JsonString); - FNakamaPartyClose(); -}; - -// Incoming party data delivered from the server. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyData -{ - GENERATED_BODY() - - // The unique party identifier of the closing party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // A reference to the user presence that sent this data, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FNakamaUserPresence Presence; - - // A reference to the user presence that sent this data, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - int64 OpCode; - - // A reference to the user presence that sent this data, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString Data; // NBytes - - FNakamaPartyData(const FString& JsonString); - FNakamaPartyData(); -}; - - -// Announcement of a new party leader. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyLeader -{ - GENERATED_BODY() - - // The ID of the party to announce the new leader for. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // The presence of the new party leader. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FNakamaUserPresence Presence; - - FNakamaPartyLeader(const FString& JsonString); - FNakamaPartyLeader(); -}; - - -// Presence update for a particular party. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyPresenceEvent -{ - GENERATED_BODY() - - // The ID of the party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString PartyId; - - // The user presences that have just joined the party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - TArray Joins; - - // The user presences that have just left the party. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - TArray Leaves; - - FNakamaPartyPresenceEvent(const FString& JsonString); - FNakamaPartyPresenceEvent(); -}; - -// List of realtime parties. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaPartyList -{ - GENERATED_BODY() - - // A number of parties corresponding to a list operation. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - TArray Parties; - - // A cursor to send when retrieving the next page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Parties") - FString Cursor; - - FNakamaPartyList(const FString& JsonString); - FNakamaPartyList(); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaPresence.h b/Nakama/Source/NakamaUnreal/Public/NakamaPresence.h deleted file mode 100644 index c63670b8e..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaPresence.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.generated.h" - - -UENUM(BlueprintType) -enum class ENakamaPresenceEvent : uint8 -{ - None, - LEAVES UMETA(DisplayName = "Leaves"), - JOINS UMETA(DisplayName = "Joins"), -}; - - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUserPresence -{ - GENERATED_BODY() - - // The user this presence belongs to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence", meta = (JsonProperty = "user_id")) - FString UserID; - - // A unique session ID identifying the particular connection, because the user may have many. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence", meta = (JsonProperty = "session_id")) - FString SessionID; - - // The username for display purposes. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence", meta = (JsonProperty = "username")) - FString Username; - - // Whether this presence generates persistent data/messages, if applicable for the stream type. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence", meta = (JsonProperty = "persistence")) - bool Persistence = false; - - // A user-set status message for this stream, if applicable. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence") - FString Status; - - FNakamaUserPresence(const FString& JsonString); - FNakamaUserPresence(const TSharedPtr JsonObject); - FNakamaUserPresence(); - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaRPC.h b/Nakama/Source/NakamaUnreal/Public/NakamaRPC.h deleted file mode 100644 index 360f7cd8c..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaRPC.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRPC.generated.h" - -// RPC -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaRPC -{ - GENERATED_BODY() - - // The identifier of the function. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RPC") - FString Id; - - // The payload of the function which must be a JSON object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RPC") - FString Payload; - - // The authentication key used when executed as a non-client HTTP request. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RPC") - FString HttpKey; - - FNakamaRPC(const FString& JsonString); - FNakamaRPC(FString&& JsonString); - FNakamaRPC(); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeClient.h b/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeClient.h deleted file mode 100644 index f357d00da..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeClient.h +++ /dev/null @@ -1,1435 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRtError.h" -#include "NakamaChannelTypes.h" -#include "NakamaMatch.h" -#include "NakamaMatchTypes.h" -#include "NakamaNotification.h" -#include "NakamaParty.h" -#include "NakamaPresence.h" -#include "NakamaStatus.h" -#include "NakamaStreams.h" -#include "NakamaChat.h" -#include "Tickable.h" -#include "IWebSocket.h" -#include "NakamaRealtimeRequestContext.h" -#include "NakamaRPC.h" -#include "Engine/TimerHandle.h" - -#include "NakamaRealtimeClient.generated.h" - -// --- Bindable Delegates --- // - -// OnConnect -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnect); -DECLARE_MULTICAST_DELEGATE(FOnConnectNative); - -// OnConnectionError -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedConnectionError, const FNakamaRtError&, Error); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedConnectionErrorNative, const FNakamaRtError&); - -// OnDisconnect -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDisconnected, const FNakamaDisconnectInfo&, DisconnectInfo); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnDisconnectedNative, const FNakamaDisconnectInfo&); - -// OnError -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedError, const FNakamaRtError&, Error); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedErrorNative, const FNakamaRtError&); - -// OnChannelMessage -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedChannelMessage, const FNakamaChannelMessage&, ChannelMessage); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedChannelMessageNative, const FNakamaChannelMessage&); - -// OnChannelPresenceEvent -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedChannelPresenceEvent, const FNakamaChannelPresenceEvent&, ChannelPresenceEvent); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedChannelPresenceEventNative, const FNakamaChannelPresenceEvent&); - -// OnMatchmakerMatched -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchmakerMatched, const FNakamaMatchmakerMatched&, Match); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchmakerMatchedNative, const FNakamaMatchmakerMatched&); - -// OnMatchPresenceEvent -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchPresenceCallback, const FNakamaMatchPresenceEvent&, PresenceEvent); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchPresenceCallbackNative, const FNakamaMatchPresenceEvent&); - -// OnMatchData -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchData, const FNakamaMatchData&, MatchData); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedMatchDataNative, const FNakamaMatchData&); - -// OnNotifications -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedNotification, const FNakamaNotificationList&, NotificationData); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedNotificationNative, const FNakamaNotificationList&); - -// OnStatusPresenceEvent -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedStatusPresence, const FNakamaStatusPresenceEvent&, UserPresenceData); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedStatusPresenceNative, const FNakamaStatusPresenceEvent&); - -// OnStreamPresenceEvent -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedStreamPresenceEvent, const FNakamaStreamPresenceEvent&, StreamPresence); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedStreamPresenceEventNative, const FNakamaStreamPresenceEvent&); - -// OnStreamData -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedStreamPresenceData, const FNakamaStreamData&, StreamPresenceData); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedStreamPresenceDataNative, const FNakamaStreamData&); - -// --- Bindable Delegates: Parties --- // - -// OnParty -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedParty, const FNakamaParty&, Party); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyNative, const FNakamaParty&); - -// OnPartyClose -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyClose, const FNakamaPartyClose&, PartyClose); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyCloseNative, const FNakamaPartyClose&); - -// OnPartyData -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyData, const FNakamaPartyData&, PartyData); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyDataNative, const FNakamaPartyData&); - -// OnPartyJoinRequest -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyJoinRequest, const FNakamaPartyJoinRequest&, PartyJoinRequest); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyJoinRequestNative, const FNakamaPartyJoinRequest&); - -// OnPartyLeader -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyLeader, const FNakamaPartyLeader&, PartyLeader); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyLeaderNative, const FNakamaPartyLeader&); - -// OnPartyMatchmakerTicket -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyMatchmakerTicket, const FNakamaPartyMatchmakerTicket&, Ticket); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyMatchmakerTicketNative, const FNakamaPartyMatchmakerTicket&); - -// OnPartyPresence -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyPresence, const FNakamaPartyPresenceEvent&, Presences); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnReceivedPartyPresenceNative, const FNakamaPartyPresenceEvent&); - - -// Functionality Delegates -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRtError, const FNakamaRtError&, ErrorData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJoinChat, FNakamaChannel, Channel); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWriteChatMessage, FNakamaChannelMessageAck, ChannelMessage); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLeaveChat); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMatchmakerTicket, FString, ticket); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRemovedMatchmakerTicket, FString, ticket); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSetStatus); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUnFollowUsers); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFollowUsers, const FNakamaStatus&, Status); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCreateMatch, FNakamaMatch, Match); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLeaveMatch); - -// Functionality Delegates - Parties -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCreateParty, FNakamaParty, Party); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJoinParty, FString, PartyId); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnLeaveParty); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListPartyJoinRequests, FNakamaPartyJoinRequest, JoinRequest); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPromotePartyMember); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRemoveMatchmakerParty, FString, ticket); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRemovePartyMember); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAcceptPartyMember); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAddMatchmakerParty, FNakamaPartyMatchmakerTicket, Ticket); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCloseParty); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRealtimeClientConnected); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRealtimeClientError, const FNakamaRtError&, ErrorData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRealtimeClientConnectionError, const FNakamaRtError&, ErrorData); - - -// RPC -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRtRPC, const FNakamaRPC&, rpc); - -/** - * - */ - -UCLASS(Blueprintable, BlueprintType, meta=(BlueprintSpawnableComponent)) -class NAKAMAUNREAL_API UNakamaRealtimeClient : public UObject, public FTickableGameObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - bool bIsActive; - - // Internal, do not use, use SetupRealtimeClient from NakamaClient instead - void Initialize(const FString& InHost, int32 InPort, bool InSSL); - - // Call this before calling Connect to use your own websocket implementation. - // Pass nullpointer to go back to default UNakamaRealtimeClient behaviour. - // This will disconnect existing websocket connection if UNakamaRealtimeClient was already connected! - // IWebSocket->Connect() should not have been called on this websocket when passing it here. It will be managed by UNakamaRealtimeClient.Connect(...); - void UseCustomWebsocket(TSharedPtr CustomWebSocket); - - /** - * Connect to the Server. - * - * @param Session The Session to use. - * @param bCreateStatus Show as online. - * @param Success Delegate called upon a successful connection to the server. - * @param ConnectionError Delegate called when a connection error occurs. Provides detailed error information. - */ - UFUNCTION(Category = "Nakama|Realtime") - void Connect( - UNakamaSession* Session, - bool bCreateStatus, - const FOnRealtimeClientConnected& Success, - const FOnRealtimeClientConnectionError& ConnectionError - ); - - /** - * Connect to the Server using lambdas - * - * @param Session The Session to use. - * @param bCreateStatus Show as online. - * @param Success Callback invoked when successfully connected to the server. - * @param ConnectionError Callback invoked when a connection error occurs. Provides detailed error information. - * - */ - void Connect( - UNakamaSession* Session, - bool bCreateStatus, - TFunction Success = nullptr, - TFunction ConnectionError = nullptr - ); - - // Events (bindable from blueprints or c++) - - // OnConnect - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnConnect")) - FOnConnect ConnectedEvent; - FOnConnectNative ConnectedEventNative; - - // OnConnectionError - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnConnectionError")) - FOnReceivedConnectionError ConnectionErrorEvent; - FOnReceivedConnectionErrorNative ConnectionErrorEventNative; - - // OnDisconnect - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnDisconnect")) - FOnDisconnected DisconnectedEvent; - FOnDisconnectedNative DisconnectedEventNative; - - // OnError - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnError")) - FOnReceivedError ErrorEvent; - FOnReceivedErrorNative ErrorEventNative; - - // OnChannelMessage - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnChannelMessage")) - FOnReceivedChannelMessage ChannelMessageReceived; - FOnReceivedChannelMessageNative ChannelMessageReceivedNative; - - // OnChannelPresenceEvent - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnChannelPresenceEvent")) - FOnReceivedChannelPresenceEvent ChannelPresenceEventReceived; - FOnReceivedChannelPresenceEventNative ChannelPresenceEventReceivedNative; - - // OnMatchmakerMatched - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnMatchmakerMatched")) - FOnReceivedMatchmakerMatched MatchmakerMatchMatched; - FOnReceivedMatchmakerMatchedNative MatchmakerMatchMatchedNative; - - // OnMatchPresenceEvent - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnMatchPresenceEvent")) - FOnReceivedMatchPresenceCallback MatchmakerPresenceCallback; - FOnReceivedMatchPresenceCallbackNative MatchmakerPresenceCallbackNative; - - // OnMatchData - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnMatchData")) - FOnReceivedMatchData MatchDataCallback; - FOnReceivedMatchDataNative MatchDataCallbackNative; - - // OnNotifications - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnNotifications")) - FOnReceivedNotification NotificationReceived; - FOnReceivedNotificationNative NotificationReceivedNative; - - // OnStatusPresenceEvent - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnStatusPresenceEvent")) - FOnReceivedStatusPresence PresenceStatusReceived; - FOnReceivedStatusPresenceNative PresenceStatusReceivedNative; - - // OnStreamPresenceEvent - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnStreamPresenceEvent")) - FOnReceivedStreamPresenceEvent StreamPresenceEventReceived; - FOnReceivedStreamPresenceEventNative StreamPresenceEventReceivedNative; - - // OnStreamData - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnStreamData")) - FOnReceivedStreamPresenceData StreamPresenceDataReceived; - FOnReceivedStreamPresenceDataNative StreamPresenceDataReceivedNative; - - // OnParty - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnParty")) - FOnReceivedParty PartyReceived; - FOnReceivedPartyNative PartyReceivedNative; - - // OnPartyClose - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyClose")) - FOnReceivedPartyClose PartyCloseReceived; - FOnReceivedPartyCloseNative PartyCloseReceivedNative; - - // OnPartyData - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyData")) - FOnReceivedPartyData PartyDataReceived; - FOnReceivedPartyDataNative PartyDataReceivedNative; - - // OnPartyJoinRequest - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyJoinRequest")) - FOnReceivedPartyJoinRequest PartyJoinRequestReceived; - FOnReceivedPartyJoinRequestNative PartyJoinRequestReceivedNative; - - // OnPartyLeader - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyLeader")) - FOnReceivedPartyLeader PartyLeaderReceived; - FOnReceivedPartyLeaderNative PartyLeaderReceivedNative; - - // OnPartyMatchmakerTicket - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyMatchmakerTicket")) - FOnReceivedPartyMatchmakerTicket PartyMatchmakerTicketReceived; - FOnReceivedPartyMatchmakerTicketNative PartyMatchmakerTicketReceivedNative; - - // OnPartyPresence - UPROPERTY(BlueprintAssignable, Category = "Nakama|Events", meta = (DisplayName = "OnPartyPresence")) - FOnReceivedPartyPresence PartyPresenceReceived; - FOnReceivedPartyPresenceNative PartyPresenceReceivedNative; - - // Functionaliy Events - FOnWriteChatMessage ChannelMessageWrite; - - - // [DEPRECATED] Listener Events - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerAllCallbacks(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerConnectCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerConnectionErrorCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerDisconnectCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerErrorCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerChannelMessageCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerChannelPresenceCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerMatchmakerMatchedCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerMatchDataCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerMatchPresenceCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerNotificationsCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyCloseCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyDataCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyJoinRequestCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyLeaderCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyMatchmakerTicketCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerPartyPresenceCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerStatusPresenceCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerStreamPresenceCallback(); - - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Listeners", - meta=(DeprecatedFunction, DeprecationMessage="You no longer need to call this, bind to the events directly instead")) - void SetListenerStreamDataCallback(); - - /** - * Destroys the Realtime Client. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Client") - void Destroy(); - - // Event that is called on cleanup - virtual void BeginDestroy() override; - - /** - * Disconnects the client. This function kills all outgoing exchanges immediately without waiting. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Authentication|Disconnect") - void Disconnect(); - - - // --- FUNCTIONALITY --- // - - // --- Messaging --- // - - /** - * [DEPRECATED] Send a chat message to a channel on the server. - * - * @param ChannelId The channel to send on. - * @param Content The content of the chat message. Must be a JSON object. - * @param Success Delegate called on successful chat message delivery. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat|Messaging", meta=(DeprecatedFunction, DeprecationMessage="Use WriteChatMessage instead")) - void SendMessage( - const FString& ChannelId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error - ); - - /** - * Send a chat message to a channel on the server. - * - * @param ChannelId The channel to send on. - * @param Content The content of the chat message. Must be a JSON object. - * @param Success Delegate called on successful chat message delivery. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat|Messaging") - void WriteChatMessage( - const FString& ChannelId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error - ); - - /** - * Send a direct chat message to another user. - * - * @param UserID The user to send to. - * @param Content The content of the chat message. Must be a JSON object. - * @param Success Delegate called on successful chat message delivery. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat|Messaging") - void SendDirectMessage( - const FString& UserID, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error - ); - - /** - * Update a chat message to a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to update. - * @param Content The content update for the message. Must be a JSON object. - * @param Success Delegate called when the chat message is successfully updated. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat|Messaging") - void UpdateChatMessage( - const FString& ChannelId, - const FString& MessageId, - const FString& Content, - FOnWriteChatMessage Success, - FOnRtError Error - ); - - /** - * Remove a chat message from a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to remove. - * @param Success Delegate called when the chat message is successfully removed. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat|Messaging") - void RemoveChatMessage( - const FString& ChannelId, - const FString& MessageId, - FOnWriteChatMessage Success, - FOnRtError Error - ); - - // --- Chat --- // - - /** - * Join a chat channel on the server. - * - * @param ChatId The target channel to join. - * @param ChannelType The type of channel to join. - * @param Persistence True if chat messages should be stored. - * @param Hidden True if the user should be hidden on the channel. - * @param Success Delegate called when successfully joined the chat channel. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat") - void JoinChat( - const FString& ChatId, - ENakamaChannelType ChannelType, - bool Persistence, - bool Hidden, - FOnJoinChat Success, - FOnRtError Error - ); - - /** - * Leave a chat channel on the server. - * - * @param ChannelId The channel to leave. - * @param Success Delegate called after successfully leaving the chat channel. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Chat") - void LeaveChat( - const FString& ChannelId, - FOnLeaveChat Success, - FOnRtError Error - ); - - // NOTE: List Chat Messages are done in normal client - - // --- Matchmaker --- // - - /** - * Join the matchmaker pool and search for opponents on the server. - * - * @param MinCount The minimum number of players to compete against. - * @param MaxCount The maximum number of players to compete against. - * @param Query A matchmaker query to search for opponents. - * @param StringProperties A set of k/v properties to provide in searches. - * @param NumericProperties A set of k/v numeric properties to provide in searches. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - * @param IgnoreCountMultiple An optional boolean flag indicating whether the matchmaker should ignore the count multiple during the search. - * @param Success Delegate called when a matchmaker ticket is successfully acquired. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Matchmaker") - void AddMatchmaker( - int32 MinCount, - int32 MaxCount, - const FString& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - int32 CountMultiple, - bool IgnoreCountMultiple, - FOnMatchmakerTicket Success, - FOnRtError Error - ); - - /** - * [DEPRECATED] Leave the matchmaker pool by ticket. - * - * @param Ticket The ticket returned by the matchmaker on join. See NMatchmakerTicket.ticket. - * @param Success Delegate called when successfully removed from the matchmaker using the provided ticket. - * @param Success Delegate called when successfully removed from the matchmaker using the provided ticket. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Matchmaker", meta=(DeprecatedFunction, DeprecationMessage="Use RemoveMatchmaker instead")) - void LeaveMatchmaker( - const FString& Ticket, - FOnRemovedMatchmakerTicket Success, - FOnRtError Error - ); - - /** - * Leave the matchmaker pool by ticket. - * - * @param Ticket The ticket returned by the matchmaker on join. See NMatchmakerTicket.ticket. - * @param Success Delegate called when successfully removed from the matchmaker using the provided ticket. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Matchmaker") - void RemoveMatchmaker( - const FString& Ticket, - FOnRemovedMatchmakerTicket Success, - FOnRtError Error - ); - - // --- Statuses --- // - - /** - * Update the user's status online. - * - * @param StatusMessage The new status of the user. - * @param Success Delegate called when the user's online status is successfully updated. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Status") - void UpdateStatus( - const FString& StatusMessage, - FOnSetStatus Success, - FOnRtError Error - ); - - /** - * Update the user's status to offline, appearing invisible to others. - * - * @param Success Delegate called when the user's status is successfully set to offline. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Status") - void SetAppearOffline( - FOnSetStatus Success, - FOnRtError Error - ); - - /** - * Follow one or more users for status updates. - * - * @param UserIds The user Ids to follow. - * @param Error Delegate called if an error occurs, detailing the failure. - * @param Success Delegate called when the users are successfully followed for status updates. - */ - UFUNCTION(Category = "Nakama|Status") - void FollowUsers( - const TArray& UserIds, - FOnFollowUsers Success, - FOnRtError Error - ); - - /** - * Unfollow status updates for one or more users. - * - * @param UserIds The ids of users to unfollow. - * @param Success Delegate called when status updates for the specified users are successfully unfollowed. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Status") - void UnFollowUsers( - const TArray& UserIds, - FOnUnFollowUsers Success, - FOnRtError Error - ); - - // --- Realtime and Match (To send RPC, please use normal Client) --- // - - /** - * Create a multiplayer match on the server. - * @param Success Delegate called when a multiplayer match is successfully created on the server. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Match") - void CreateMatch( - FOnCreateMatch Success, - FOnRtError Error - ); - - /** - * Join a multiplayer match by ID. - * - * @param MatchId A match ID. - * @param MetaData Metadata. - * @param Success Delegate called when successfully joined a match by its ID. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Match") - void JoinMatch( - const FString& MatchId, - const TMap& MetaData, - FOnCreateMatch Success, - FOnRtError Error - ); - - /** - * Join a multiplayer match with a matchmaker. - * - * @param Token A matchmaker ticket result object. - * @param Success Delegate called when successfully joined a match using a matchmaker token. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Match") - void JoinMatchByToken( - const FString& Token, - FOnCreateMatch Success, - FOnRtError Error - ); - - /** - * Send a state change to a match on the server. - * - * When no presences are supplied the new match state will be sent to all presences. - * - * @param MatchId The Id of the match. - * @param OpCode An operation code for the match state. - * @param Data The new state to send to the match. - * @param Presences The presences in the match to send the state. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Match") // BlueprintCallable since it is shared with Blueprints - void SendMatchData( - const FString& MatchId, - int64 OpCode, - const FString& Data, - const TArray& Presences - ); - - /** - * Leave a match on the server. - * - * @param MatchId The match to leave. - * @param Success Delegate called when successfully left a match on the server. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Match") - void LeaveMatch( - const FString& MatchId, - FOnLeaveMatch Success, - FOnRtError Error - ); - - // --- Parties --- // - - /** - * Create a party. - * @param Open Whether or not the party will require join requests to be approved by the party leader. - * @param MaxSize Maximum number of party members. - * @param Success Delegate called when a party is successfully created, returning the Party. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void CreateParty( - bool Open, - int32 MaxSize, - FOnCreateParty Success, - FOnRtError Error - ); - - /** - * Join a party. - * @param PartyId Party ID. - * @param Success Delegate called when successfully joined a party. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void JoinParty( - const FString& PartyId, - FOnJoinParty Success, - FOnRtError Error - ); - - /** - * Leave the party. - * @param PartyId Party ID. - * @param Success Delegate called when successfully left a party. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void LeaveParty( - const FString& PartyId, - FOnLeaveParty Success, - FOnRtError Error - ); - - /** - * Request a list of pending join requests for a party. - * @param PartyId Party ID. - * @param Success Delegate called when a list of pending join requests for a party is successfully fetched. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void ListPartyJoinRequests( - const FString& PartyId, - FOnListPartyJoinRequests Success, - FOnRtError Error - ); - - /** - * Promote a new party leader. - * @param PartyId Party ID. - * @param PartyMember The presence of an existing party member to promote as the new leader. - * @param Success Delegate called when a party member is successfully promoted as the new leader. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void PromotePartyMember( - const FString& PartyId, - const FNakamaUserPresence& PartyMember, - FOnPromotePartyMember Success, - FOnRtError Error - ); - - /** - * Cancel a party matchmaking process using a ticket. - * @param PartyId Party ID. - * @param Ticket The ticket to cancel. - * @param Success Delegate called when the party matchmaking process is successfully canceled using a ticket. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void RemoveMatchMakerParty( - const FString& PartyId, - const FString& Ticket, - FOnRemoveMatchmakerParty Success, - FOnRtError Error - ); - - /** - * Kick a party member, or decline a request to join. - * @param PartyId Party ID to remove/reject from. - * @param Presence The presence to remove or reject. - * @param Success Delegate called when a party member is successfully kicked or a join request is declined. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void RemovePartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - FOnRemovePartyMember Success, - FOnRtError Error - ); - - /** - * Send data to a party. - * @param PartyId Party ID to send to. - * @param OpCode Op code value. - * @param Data The input data to send from the byte buffer, if any. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime|Parties") // BlueprintCallable since it is shared with Blueprints - void SendPartyData( - const FString& PartyId, - int64 OpCode, - const FString& Data - ); - - /** - * Accept a party member's request to join the party. - * - * @param PartyId The party ID to accept the join request for. - * @param Presence The presence to accept as a party member. - * @param Success Delegate called when a party member's request to join the party is successfully accepted. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void AcceptPartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - FOnAcceptPartyMember Success, - FOnRtError Error - ); - - /** - * Begin matchmaking as a party. - * @param PartyId Party ID. - * @param Query Filter query used to identify suitable users. - * @param MinCount Minimum total user count to match together. - * @param MaxCount Maximum total user count to match together. - * @param StringProperties String properties. - * @param NumericProperties Numeric properties. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - * @param IgnoreCountMultiple An optional boolean flag indicating whether the matchmaker should ignore the count multiple during the search. - * @param Success Delegate called when matchmaking as a party has been initiated successfully. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void AddMatchmakerParty( - const FString& PartyId, - const FString& Query, - int32 MinCount, - int32 MaxCount, - const TMap& StringProperties, - const TMap& NumericProperties, - int32 CountMultiple, - bool IgnoreCountMultiple, - FOnAddMatchmakerParty Success, - FOnRtError Error - ); - - /** - * End a party, kicking all party members and closing it. - * @param PartyId The ID of the party. - * @param Success Delegate called when a party has been successfully closed, and all party members have been kicked. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|Parties") - void CloseParty( - const FString& PartyId, - FOnCloseParty Success, - FOnRtError Error - ); - - // --- RPC --- // - - /** - * Send an RPC message to the server with the realtime client. - * - * @param FunctionId The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param Success Delegate called when the RPC message has been successfully processed by the server and a response is received. - * @param Error Delegate called if an error occurs, detailing the failure. - */ - UFUNCTION(Category = "Nakama|Realtime|RPC") - void RPC( - const FString& FunctionId, - const FString& Payload, - FOnRtRPC Success, - FOnRtError Error - ); - - // TFunctions - - /** - * Join a chat channel on the server. - * - * @param Target The target channel to join. - * @param ChannelType The type of channel to join. - * @param Persistence True if chat messages should be stored. - * @param Hidden True if the user should be hidden on the channel. - * @param SuccessCallback Callback invoked called when successfully joined the chat channel. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinChat( - const FString& Target, - ENakamaChannelType ChannelType, - TOptional Persistence, - TOptional Hidden, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Leave a chat channel on the server. - * - * @param ChannelId The channel to leave. - * @param SuccessCallback Callback invoked after successfully leaving the chat channel. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void LeaveChat( - const FString& ChannelId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Send a chat message to a channel on the server. - * - * @param ChannelId The channel to send on. - * @param Content The content of the chat message. Must be a JSON object. - * @param SuccessCallback Callback invoked on successful chat message delivery. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void WriteChatMessage( - const FString& ChannelId, - const FString& Content, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Update a chat message to a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to update. - * @param Content The content update for the message. Must be a JSON object. - * @param SuccessCallback Callback invoked when the chat message is successfully updated. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void UpdateChatMessage( - const FString& ChannelId, - const FString& MessageId, - const FString& Content, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Remove a chat message from a channel on the server. - * - * @param ChannelId The ID of the chat channel with the message. - * @param MessageId The ID of the message to remove. - * @param SuccessCallback Callback invoked when the chat message is successfully removed. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void RemoveChatMessage( - const FString& ChannelId, - const FString& MessageId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Create a multiplayer match on the server. - * @param SuccessCallback Callback invoked when a multiplayer match is successfully created on the server. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void CreateMatch( - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join a multiplayer match by ID. - * - * @param MatchId A match ID. - * @param Metadata Metadata. - * @param SuccessCallback Callback invoked when successfully joined a match by its ID. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinMatch( - const FString& MatchId, - const TMap& Metadata, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join a multiplayer match with a matchmaker. - * - * @param Token A matchmaker ticket result object. - * @param SuccessCallback Callback invoked when successfully joined a match using a matchmaker token. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinMatchByToken( - const FString& Token, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Leave a match on the server. - * - * @param MatchId The match to leave. - * @param SuccessCallback Delegate called when successfully left a match on the server. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void LeaveMatch( - const FString& MatchId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join the matchmaker pool and search for opponents on the server. - * - * @param MinCount The minimum number of players to compete against. - * @param MaxCount The maximum number of players to compete against. - * @param Query A matchmaker query to search for opponents. - * @param StringProperties A set of k/v properties to provide in searches. - * @param NumericProperties A set of k/v numeric properties to provide in searches. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - * @param SuccessCallback Callback invoked when a matchmaker ticket is successfully acquired. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void AddMatchmaker( - TOptional& MinCount, - TOptional& MaxCount, - const TOptional& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - const TOptional& CountMultiple, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Cancel a party matchmaking process using a ticket. - * @param Ticket The ticket to cancel. - * @param SuccessCallback Callback invoked when successfully removed from the matchmaker using the provided ticket. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void RemoveMatchmaker( - const FString& Ticket, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Follow one or more users for status updates. - * - * @param UserIds The user Ids to follow. - * @param SuccessCallback Callback invoked when the users are successfully followed for status updates. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void FollowUsers( - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Unfollow status updates for one or more users. - * - * @param UserIds The ids of users to unfollow. - * @param SuccessCallback Callback invoked when status updates for the specified users are successfully unfollowed. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void UnfollowUsers( - const TArray& UserIds, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Update the user's status online. - * - * @param Status The new status of the user. - * @param SuccessCallback Callback invoked when the user's online status is successfully updated. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void UpdateStatus( - const FString& Status, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Send an RPC message to the server with the realtime client. - * - * @param Id The ID of the function to execute. - * @param Payload The string content to send to the server. - * @param SuccessCallback Callback invoked when the RPC message has been successfully processed by the server and a response is received. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void RPC( - const FString& Id, - const TOptional& Payload, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // Party System - - /** - * Accept a party member's request to join the party. - * - * @param PartyId The party ID to accept the join request for. - * @param Presence The presence to accept as a party member. - * @param SuccessCallback Callback invoked when a party member's request to join the party is successfully accepted. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void AcceptPartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Begin matchmaking as a party. - * @param PartyId Party ID. - * @param Query Filter query used to identify suitable users. - * @param MinCount Minimum total user count to match together. - * @param MaxCount Maximum total user count to match together. - * @param StringProperties String properties. - * @param NumericProperties Numeric properties. - * @param CountMultiple An optional multiple of the matched count that must be satisfied. - * @param SuccessCallback Callback invoked when matchmaking as a party has been initiated successfully. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void AddMatchmakerParty( - const FString& PartyId, - TOptional& MinCount, - TOptional& MaxCount, - const TOptional& Query, - const TMap& StringProperties, - const TMap& NumericProperties, - const TOptional& CountMultiple, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * End a party, kicking all party members and closing it. - * @param PartyId The ID of the party. - * @param SuccessCallback Callback invoked when a party has been successfully closed, and all party members have been kicked. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void CloseParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Create a party. - * @param bOpen Whether or not the party will require join requests to be approved by the party leader. - * @param MaxSize Maximum number of party members. - * @param SuccessCallback Delegate called when a party is successfully created, returning the Party. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void CreateParty( - bool bOpen, - int32 MaxSize, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Join a party. - * @param PartyId Party ID. - * @param SuccessCallback Callback invoked when successfully joined a party. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void JoinParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Leave the party. - * @param PartyId Party ID. - * @param SuccessCallback Callback invoked when successfully left a party. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void LeaveParty( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Request a list of pending join requests for a party. - * @param PartyId Party ID. - * @param SuccessCallback Delegate called when a list of pending join requests for a party is successfully fetched. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void ListPartyJoinRequests( - const FString& PartyId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Promote a new party leader. - * @param PartyId Party ID. - * @param PartyMember The presence of an existing party member to promote as the new leader. - * @param SuccessCallback Callback invoked when a party member is successfully promoted as the new leader. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void PromotePartyMember( - const FString& PartyId, - const FNakamaUserPresence& PartyMember, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Cancel a party matchmaking process using a ticket. - * @param PartyId Party ID. - * @param Ticket The ticket to cancel. - * @param SuccessCallback Callback invoked when the party matchmaking process is successfully canceled using a ticket. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void RemoveMatchmakerParty( - const FString& PartyId, - const FString& Ticket, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - /** - * Kick a party member, or decline a request to join. - * @param PartyId Party ID to remove/reject from. - * @param Presence The presence to remove or reject - * @param SuccessCallback Callback invoked when a party member is successfully kicked or a join request is declined. - * @param ErrorCallback Callback invoked if an error occurs, detailing the failure. - */ - void RemovePartyMember( - const FString& PartyId, - const FNakamaUserPresence& Presence, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // Helpers/Utilities - - /** - * @return True if connected to server. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Realtime") - bool IsConnected() const; - - /** - * Get heartbeat interval in milliseconds. - * - * @return heartbeat interval value or opt::nullopt if disabled - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Realtime") - int32 GetHeartbeatIntervalMs() const; - - /** - * Set heartbeat interval in milliseconds. Disconnect event will be - * detected in at most 2 x interval. - * - * Default is 5 seconds. - * - * @param IntervalMs interval in ms send heartbeats in. - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Realtime") - void SetHeartbeatIntervalMs(int32 IntervalMs); - - // Creates a request context and assigns a CID to the outgoing message. - TObjectPtr CreateReqContext(FNakamaRealtimeEnvelope& envelope); - - // Reusable functionality to handle Sending messages with Envelopes (with callbacks) - void SendMessageWithEnvelope(const FString& FieldName, const TSharedPtr& ObjectField, TFunction SuccessCallback, TFunction ErrorCallback); - void SendMessageWithEnvelopeMove(const FString& FieldName, const TSharedPtr& ObjectField, TFunction SuccessCallback, TFunction ErrorCallback); - - // Reusable functionality to handle Sending messages with Envelopes (no callbacks) - void SendDataWithEnvelope(const FString& FieldName, const TSharedPtr& ObjectField); - - // --- Listener Events Section --- // - - // These should be used when binding to the lambdas, but only one can bind to it at once - // use dynamic multicast delegates instead if you wish to bind to it multiple places - void SetConnectCallback(const TFunction& Callback) { OnConnect = Callback; } - void SetConnectionErrorCallback(const TFunction& Callback) { OnConnectionError = Callback; } - void SetDisconnectCallback(const TFunction& Callback) { OnDisconnect = Callback; } - void SetErrorCallback(const TFunction& Callback) { OnError = Callback; } - void SetChannelMessageCallback(const TFunction& Callback) { OnChannelMessage = Callback; } - void SetChannelPresenceCallback(const TFunction& Callback) { OnChannelPresenceEvent = Callback; } - void SetMatchmakerMatchedCallback(const TFunction& Callback) { OnMatchmakerMatched = Callback; } - void SetMatchDataCallback(const TFunction& Callback) { OnMatchData = Callback; } - void SetMatchPresenceCallback(const TFunction& Callback) { OnMatchPresenceEvent = Callback; } - void SetNotificationsCallback(const TFunction& Callback) { OnNotifications = Callback; } - void SetPartyCallback(const TFunction& Callback) { OnParty = Callback; } - void SetPartyCloseCallback(const TFunction& Callback) { OnPartyClose = Callback; } - void SetPartyDataCallback(const TFunction& Callback) { OnPartyData = Callback; } - void SetPartyJoinRequestCallback(const TFunction& Callback) { OnPartyJoinRequest = Callback; } - void SetPartyLeaderCallback(const TFunction& Callback) { OnPartyLeader = Callback; } - void SetPartyMatchmakerTicketCallback(const TFunction& Callback) { OnPartyMatchmakerTicket = Callback; } - void SetPartyPresenceCallback(const TFunction& Callback) { OnPartyPresenceEvent = Callback; } - void SetStatusPresenceCallback(const TFunction& Callback) { OnStatusPresenceEvent = Callback; } - void SetStreamPresenceCallback(const TFunction& Callback) { OnStreamPresenceEvent = Callback; } - void SetStreamDataCallback(const TFunction& Callback) { OnStreamData = Callback; } - -private: - - // The actual Lambdas that will be called within this class - TFunction OnConnect; - TFunction OnConnectionError; - TFunction OnDisconnect; - TFunction OnError; - TFunction OnChannelMessage; - TFunction OnChannelPresenceEvent; - TFunction OnMatchmakerMatched; - TFunction OnMatchPresenceEvent; - TFunction OnMatchData; - TFunction OnNotifications; - TFunction OnStatusPresenceEvent; - TFunction OnStreamPresenceEvent; - TFunction OnStreamData; - TFunction OnParty; - TFunction OnPartyClose; - TFunction OnPartyData; - TFunction OnPartyJoinRequest; - TFunction OnPartyLeader; - TFunction OnPartyMatchmakerTicket; - TFunction OnPartyPresenceEvent; - - // WebSocket Instance - TSharedPtr WebSocket; - - // Params - FString Host; - int32 Port; - bool bUseSSL; - bool bIsCustomWebsocketSet = false; - - UPROPERTY() - bool bShowAsOnline; - - // Heartbeat - UPROPERTY() - FTimerHandle HeartbeatTimerHandle; - - float HeartbeatIntervalMs = 3000.0f; // Adjust this value as needed (3 seconds = 3000 milliseconds) - - bool bHeartbeatFailureReported = false; - double LastHeartbeatTimestamp = 0; - double LastMessageTimestamp = 0; - - bool bLocalDisconnectInitiated = false; - - void CleanupWebSocket(); - - - void SendPing(); - void Heartbeat(); - - // Handling Messages - void HandleReceivedMessage(const FString& Data); - - // Used for "ping" - void SendMessage(const FString& FieldName, const TSharedPtr& Object); - - // Heartbeat and Ticking - float AccumulatedDeltaTime = 0.0f; - - virtual void Tick(float DeltaTime) override; - virtual bool IsTickable() const override; - virtual TStatId GetStatId() const override; - - // Helpers - bool SerializeJsonObject(const TSharedPtr& JsonObject, FString& OutSerializedJson); - - void CancelAllRequests(const ENakamaRtErrorCode & ErrorCode); - void OnTransportError(const FString& Description); - - enum class EConnectionState - { - Disconnected, - Connecting, - Connected, - Disconnecting, - }; - - EConnectionState ConnectionState = EConnectionState::Disconnected; - -protected: - - // CID generator and request contexts - UPROPERTY() - TMap> ReqContexts; - int32 NextCid = 0; - FCriticalSection ReqContextsLock; -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeRequestContext.h b/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeRequestContext.h deleted file mode 100644 index a96cf93bf..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaRealtimeRequestContext.h +++ /dev/null @@ -1,87 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRtError.h" -//#include "NakamaUtils.h" -#include "Serialization/JsonSerializer.h" -#include "UObject/NoExportTypes.h" -#include "NakamaRealtimeRequestContext.generated.h" - -/** - * - */ - -USTRUCT(BlueprintType) -struct FNakamaRealtimeEnvelope -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category = "Nakama|Realtime") - int32 CID; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") - FString Payload; - - FNakamaRealtimeEnvelope() - : CID(-1) - , Payload("") - { - } - - FString EncodeJson(TSharedPtr JsonObject) - { - check(JsonObject.IsValid()); - FString OutputString; - TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); - FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); - return OutputString; - } - -}; - -// Change delegate types -DECLARE_DELEGATE_OneParam(FNakamaRealtimeSuccessCallback, const FNakamaRealtimeEnvelope&); // This basically holds the payload -DECLARE_DELEGATE_OneParam(FNakamaRealtimeSuccessCallbackMove, FNakamaRealtimeEnvelope&&); // This basically holds the payload -DECLARE_DELEGATE_OneParam(FNakamaRealtimeErrorCallback, const FNakamaRtError&); - -UCLASS() -class NAKAMAUNREAL_API UNakamaRealtimeRequestContext : public UObject -{ - GENERATED_BODY() - - -public: - UNakamaRealtimeRequestContext() {} - - // Note: In Nakama: - // Errors = RtErrorCallback (always) - // Success = std::function - - // The callback to invoke on success - FNakamaRealtimeSuccessCallback SuccessCallback; - - // The move version of callback to invoke on success - FNakamaRealtimeSuccessCallbackMove SuccessCallbackMove; - - // The callback to invoke on error - FNakamaRealtimeErrorCallback ErrorCallback; - - // The CID for the request - int32 CID; -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaRtError.h b/Nakama/Source/NakamaUnreal/Public/NakamaRtError.h deleted file mode 100644 index 1ce09780a..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaRtError.h +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaRtError.generated.h" - - -//Error Data -UENUM(BlueprintType) -enum class ENakamaRtErrorCode : uint8 -{ - // Client Side Errors - UNKNOWN = 8 UMETA(DisplayName = "UNKNOWN"), // -100 - CONNECT_ERROR = 9 UMETA(DisplayName = "CONNECT_ERROR"), // -1 - TRANSPORT_ERROR = 10 UMETA(DisplayName = "TRANSPORT_ERROR"), // -2 - DISCONNECTED = 11 UMETA(DisplayName = "DISCONNECTED"), // -3 - UNKNOWN_JSON = 12 UMETA(DisplayName = "UNKNOWN_JSON"), // -4 - - // Server Side Errors - RUNTIME_EXCEPTION = 0 UMETA(DisplayName = "RUNTIME_EXCEPTION"), - UNRECOGNIZED_PAYLOAD = 1 UMETA(DisplayName = "UNRECOGNIZED_PAYLOAD"), - MISSING_PAYLOAD = 2 UMETA(DisplayName = "MISSING_PAYLOAD"), - BAD_INPUT = 3 UMETA(DisplayName = "BAD_INPUT"), - MATCH_NOT_FOUND = 4 UMETA(DisplayName = "MATCH_NOT_FOUND"), - MATCH_JOIN_REJECTED = 5 UMETA(DisplayName = "MATCH_JOIN_REJECTED"), - RUNTIME_FUNCTION_NOT_FOUND = 6 UMETA(DisplayName = "RUNTIME_FUNCTION_NOT_FOUND"), - RUNTIME_FUNCTION_EXCEPTION = 7 UMETA(DisplayName = "RUNTIME_FUNCTION_EXCEPTION"), - - /* - * Base Nakama Enums (Changed since we can't have negatives) - UNKNOWN = -100, - - // client side errors - CONNECT_ERROR = -1, ///< Connect has failed. - TRANSPORT_ERROR = -2, ///< Transport error. - DISCONNECTED = -3, ///< Request cancelled due to transport disconnect - UNKNOWN_JSON = -4, ///< FNakamaRtError was build with a json that does not contain error code - - // server side errors - RUNTIME_EXCEPTION = 0, ///< An unexpected result from the server. - UNRECOGNIZED_PAYLOAD = 1, ///< The server received a message which is not recognised. - MISSING_PAYLOAD = 2, ///< A message was expected but contains no content. - BAD_INPUT = 3, ///< Fields in the message have an invalid format. - MATCH_NOT_FOUND = 4, ///< The match id was not found. - MATCH_JOIN_REJECTED = 5, ///< The match join was rejected. - RUNTIME_FUNCTION_NOT_FOUND = 6, ///< The runtime function does not exist on the server. - RUNTIME_FUNCTION_EXCEPTION = 7 ///< The runtime function executed with an error. - */ - -}; - -UENUM(BlueprintType) -enum class ENakamaDisconnectCode : uint8 -{ - NORMAL_CLOSURE = 0, // 1000 - GOING_AWAY = 1, // 1001 - PROTOCOL_ERROR = 2, // 1002 - UNSUPPORTED_DATA = 3, // 1003 - NO_STATUS_RCVD = 5, // 1005 - ABNORMAL_CLOSURE = 6, // 1006 - INVALID_FRAME_PAYLOAD_DATA = 7, // 1007 - POLICY_VIOLATION = 8, // 1008 - MESSAGE_TOO_BIG = 9, // 1009 - MANDATORY_EXT = 10, // 1010 - INTERNAL_SERVER_ERROR = 11, // 1011 - TLS_HANDSHAKE = 15, // 1015 - - HEARTBEAT_FAILURE = 40, // 4000 - TRANSPORT_ERROR = 41, // 4001 -}; - - -// A logical error which may occur on the server. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaRtError -{ - GENERATED_BODY() - - // A message in English to help developers debug the response. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RtError") - FString Message; - - // The error code - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RtError") - ENakamaRtErrorCode Code; - - // Additional error details which may be different for each response. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|RtError") - TMap Context; - - FNakamaRtError(const FString& JsonString); - FNakamaRtError(): Code(ENakamaRtErrorCode::UNKNOWN) { } - -}; - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaDisconnectInfo -{ - GENERATED_BODY() - - // Close Code - https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Info") - ENakamaDisconnectCode Code; - - // Close reason. Optional. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Info") - FString Reason; - - // true if close was initiated by server. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Info") - bool Remote; - - FNakamaDisconnectInfo(): Code(), Remote(false) { } - - ENakamaDisconnectCode ConvertIntToDisconnectCode(int32 Value); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaSession.h b/Nakama/Source/NakamaUnreal/Public/NakamaSession.h deleted file mode 100644 index d65165950..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaSession.h +++ /dev/null @@ -1,166 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaUserSession.h" -#include "NakamaSession.generated.h" - -/** - * - */ -UCLASS(Blueprintable, BlueprintType) -class NAKAMAUNREAL_API UNakamaSession : public UObject -{ - GENERATED_BODY() - -public: - - // Blueprint/C++ Exposed Struct with Data - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Sessions") - FNakamaUserSession SessionData; - - static UNakamaSession* SetupSession(const FString& AuthResponse); - - /** - * @return The authentication token used to construct this session. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FString GetAuthToken() const; - - /** - * @return The refresh token used to construct this session. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FString GetRefreshToken() const; - - /** - * @return True if the user account for this session was just created. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - bool IsCreated() const; - - /** - * @return The username of the user who owns this session. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FString GetUsername() const; - - /** - * @return The ID of the user who owns this session. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FString GetUserId() const; - - /** - * @return The timestamp in milliseconds when this session object was created. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FDateTime GetCreateTime() const; - - /** - * @return The timestamp in milliseconds when this session will expire. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FDateTime GetExpireTime() const; - - /** - * @return The timestamp in milliseconds when the refresh token will expire. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - const FDateTime GetRefreshExpireTime() const; - - /** - * @return True if the session has expired against the current time. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - bool IsExpired() const; - - /** - * Check if the session's token has expired against the input time. - * - * @param Time The time to compare against the session. - * Use getUnixTimestampMs() to get current time. - * @return true if the session has expired. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - bool IsExpiredTime(FDateTime Time) const; - - /** - * Check if the session's token has expired against the input time. - * - * @param now The time to compare against the session. - * Use getUnixTimestampMs() to get current time. - * @return true if the session has expired. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - bool IsRefreshExpired() const; - - /** - * Check if the session's refresh token has expired against the input time. - * - * @param Time The time to compare against the session. - * Use getUnixTimestampMs() to get current time. - * @return true if the session has expired. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - bool IsRefreshExpiredTime(FDateTime Time) const; - - /** - * Get session variables. - * - * @return Variables. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - TMap GetVariables() const; - - /** - * Get session variable value by name. - * - * @param Name Value Key - * @return Variable for this key, or empty string if not found. - */ - UFUNCTION(BlueprintPure, Category = "Nakama|Session") - FString GetVariable(FString Name) const; - - /** - * Restore User Session - * - * @param Token Authentication Token from Session - * @param RefreshToken RefreshToken retrieved from Session - */ - UFUNCTION(BlueprintCallable, Category = "Nakama|Session") - static UNakamaSession* RestoreSession(FString Token, FString RefreshToken); - -private: - - UNakamaSession() {} - - FString _AuthToken; - FString _RefreshToken; - bool _IsCreated; - FString _Username; - FString _UserId; - FDateTime _CreateTime; - FDateTime _ExpireTime; - FDateTime _RefreshExpireTime; - bool _IsExpired; - bool _IsRefreshExpired; - TMap _Variables; - - static bool ParseJwtPayload(const FString& jwt, TSharedPtr& payloadJson); -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaStatus.h b/Nakama/Source/NakamaUnreal/Public/NakamaStatus.h deleted file mode 100644 index bfcde0d75..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaStatus.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" -#include "NakamaStatus.generated.h" - -// A snapshot of statuses for some set of users. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStatus -{ - GENERATED_BODY() - - // User statuses. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence") - TArray Presences; - - FNakamaStatus(const FString& JsonString); - FNakamaStatus(); -}; - -// A batch of status updates for a given user. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStatusPresenceEvent -{ - GENERATED_BODY() - - // New statuses for the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence") - TArray Joins; - - // Previous statuses for the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User|Presence") - TArray Leaves; - - FNakamaStatusPresenceEvent(const FString& JsonString); - FNakamaStatusPresenceEvent(); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaStorageObject.h b/Nakama/Source/NakamaUnreal/Public/NakamaStorageObject.h deleted file mode 100644 index f02fa9da2..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaStorageObject.h +++ /dev/null @@ -1,213 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaStorageTypes.h" -#include "NakamaStorageObject.generated.h" - -// An object within the storage engine. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStoreObjectData -{ - GENERATED_BODY() - - // The collection which stores the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Collection; - - // The key of the object within the collection. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Key; - - // The user owner of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString UserId; - - // The value of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Value; - - // The version hash of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Version; - - // The read access permissions for the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") //BlueprintReadOnly - ENakamaStoragePermissionRead PermissionRead; //NO_READ - - // The write access permissions for the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") //BlueprintReadOnly - ENakamaStoragePermissionWrite PermissionWrite; //NO_WRITE - - // The UNIX time when the object was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FDateTime CreateTime = 0; - - // The UNIX time when the object was last updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FDateTime UpdateTime = 0; - - FNakamaStoreObjectData(const FString& JsonString); - FNakamaStoreObjectData(const TSharedPtr JsonObject); - FNakamaStoreObjectData(); -}; - -// The object to store. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStoreObjectWrite -{ - GENERATED_BODY() - - // The collection which stores the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Collection; - - // The key of the object within the collection. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Key; - - // The value of the object. Must be JSON - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Value; - - // The version hash of the object to check. Possible values are: ["", "*", "#hash#"]. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Version; - - // The read access permissions for the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") //BlueprintReadOnly - ENakamaStoragePermissionRead PermissionRead; // NO_READ - - // The write access permissions for the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") //BlueprintReadOnly - ENakamaStoragePermissionWrite PermissionWrite; // NO_WRITE - - FNakamaStoreObjectWrite(const FString& JsonString); - FNakamaStoreObjectWrite(); -}; - - - - - -// Storage objects to get (from reading) -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaReadStorageObjectId -{ - GENERATED_BODY() - - // The collection which stores the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Collection; - - // The key of the object within the collection. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Key; - - // The user owner of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString UserId; - - FNakamaReadStorageObjectId(const FString& JsonString); - FNakamaReadStorageObjectId(); -}; - - -// Storage objects to delete. -USTRUCT(BlueprintType) // For deleting from storage -struct NAKAMAUNREAL_API FNakamaDeleteStorageObjectId -{ - GENERATED_BODY() - - // The collection which stores the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Collection; - - // The key of the object within the collection. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Key; - - // The version hash of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Nakama|Storage") - FString Version; - - FNakamaDeleteStorageObjectId(const FString& JsonString); - FNakamaDeleteStorageObjectId(); -}; - - -// A storage acknowledgement. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStoreObjectAck -{ - GENERATED_BODY() - - // The collection which stores the object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FString Collection; - - // The key of the object within the collection. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FString Key; - - // The version hash of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FString Version; - - // The owner of the object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FString UserId; - - FNakamaStoreObjectAck(const FString& JsonString); - FNakamaStoreObjectAck(const TSharedPtr JsonObject); - FNakamaStoreObjectAck(); - -}; - -// Batch of acknowledgements. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStoreObjectAcks -{ - GENERATED_BODY() - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - TArray StorageObjects; - - FNakamaStoreObjectAcks(const FString& JsonString); - FNakamaStoreObjectAcks(); - -}; - -// List of storage objects. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStorageObjectList -{ - GENERATED_BODY() - - // The list of storage objects. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - TArray Objects; - - // For the next page results, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Storage") - FString Cursor; - - FNakamaStorageObjectList(const FString& JsonString); - FNakamaStorageObjectList(); - -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaStorageTypes.h b/Nakama/Source/NakamaUnreal/Public/NakamaStorageTypes.h deleted file mode 100644 index d4c0d7435..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaStorageTypes.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaStorageTypes.generated.h" - - -UENUM(BlueprintType) -enum class ENakamaStoragePermissionRead : uint8 -{ - // The object is only readable by server runtime - NO_READ = 0 UMETA(DisplayName = "No Read"), - // Only the user who owns it may access - OWNER_READ = 1 UMETA(DisplayName = "Owner Read"), - // Any user can read the object - PUBLIC_READ = 2 UMETA(DisplayName = "Public Read"), - -}; - -UENUM(BlueprintType) -enum class ENakamaStoragePermissionWrite : uint8 -{ - // The object is only writable by server runtime - NO_WRITE = 0 UMETA(DisplayName = "No Write"), - // Only the user who owns it may write - OWNER_WRITE = 1 UMETA(DisplayName = "Owner Write"), - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaStreams.h b/Nakama/Source/NakamaUnreal/Public/NakamaStreams.h deleted file mode 100644 index 5dc34bb76..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaStreams.h +++ /dev/null @@ -1,92 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaPresence.h" -#include "NakamaStreams.generated.h" - -// Represents identifying information for a stream. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStream -{ - GENERATED_BODY() - - // Mode identifies the type of stream. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - int32 Mode = 0; - - // Subject is the primary identifier, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FString Subject; - - // Subcontext is a secondary identifier, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FString SubContext; - - // The label is an arbitrary identifying string, if the stream has one. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FString Label; - - FNakamaStream(const FString& JsonString); - FNakamaStream(const TSharedPtr JsonObject); - FNakamaStream(); -}; - -// A data message delivered over a stream. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStreamData -{ - GENERATED_BODY() - - // The stream this data message relates to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FNakamaStream Stream; - - // The sender, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FNakamaUserPresence Sender; - - // Arbitrary contents of the data message. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FString Data; - - FNakamaStreamData(const FString& JsonString); - FNakamaStreamData(); -}; - -// A set of joins and leaves on a particular stream. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaStreamPresenceEvent -{ - GENERATED_BODY() - - // The stream this event relates to. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - FNakamaStream Stream; - - // Presences joining the stream as part of this event, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - TArray Joins; - - // Presences leaving the stream as part of this event, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Streams") - TArray Leaves; - - FNakamaStreamPresenceEvent(const FString& JsonString); - FNakamaStreamPresenceEvent(); -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaTournament.h b/Nakama/Source/NakamaUnreal/Public/NakamaTournament.h deleted file mode 100644 index 7f6207cd8..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaTournament.h +++ /dev/null @@ -1,147 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaLeaderboard.h" -#include "NakamaTournament.generated.h" - - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaTournament -{ - GENERATED_BODY() - - // The ID of the tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString Id; - - // The title for the tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString Title; - - // The description of the tournament. May be blank. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString Description; - - // The category of the tournament. e.g. "vip" could be category 1. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 Category = 0; - - // ASC or DESC sort mode of scores in the tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 SortOrder = 0; - - // The current number of players in the tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 Size = 0; - - // The maximum number of players for the tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 MaxSize = 0; - - // The maximum score updates allowed per player for the current tournament. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 MaxNumScore = 0; - - // True if the tournament is active and can enter. A computed value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - bool CanEnter = false; - - // The UNIX time when the tournament was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FDateTime CreateTime = 0; - - // The UNIX time when the tournament will start. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FDateTime StartTime = 0; - - // The UNIX time when the tournament will be stopped. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FDateTime EndTime = 0; - - // The UNIX time when the tournament stops being active until next reset. A computed value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 EndActive = 0; - - // The UNIX time when the tournament is next playable. A computed value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 NextReset = 0; - - // Duration of the tournament in seconds. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 Duration = 0; - - // The UNIX time when the tournament start being active. A computed value. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - int32 StartActive = 0; - - // Additional information stored as a JSON object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString Metadata; - - FNakamaTournament(const FString& JsonString); - FNakamaTournament(const TSharedPtr JsonObject); - FNakamaTournament(); - -}; - -// A set of tournament records which may be part of a tournament records page or a batch of individual records. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaTournamentRecordList -{ - GENERATED_BODY() - - // A list of tournament records. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - TArray Records; - - // A batched set of tournament records belonging to specified owners. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - TArray OwnerRecords; - - // The cursor to send when retrieving the next page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString NextCursor; - - // The cursor to send when retrieving the previous page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString PrevCursor; - - FNakamaTournamentRecordList(const FString& JsonString); - FNakamaTournamentRecordList(); - -}; - -// A list of tournaments. -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaTournamentList -{ - GENERATED_BODY() - - // A list of tournament records. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - TArray Tournaments; - - // The cursor to send when retrieving the next page, if any. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Tournaments") - FString Cursor; - - FNakamaTournamentList(const FString& JsonString); - FNakamaTournamentList(); - -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaUnreal.h b/Nakama/Source/NakamaUnreal/Public/NakamaUnreal.h deleted file mode 100644 index 2ec15a0bc..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaUnreal.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class FNakamaUnrealModule : public IModuleInterface -{ -public: - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaUser.h b/Nakama/Source/NakamaUnreal/Public/NakamaUser.h deleted file mode 100644 index d65ed2e76..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaUser.h +++ /dev/null @@ -1,113 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaUser.generated.h" - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUser -{ - GENERATED_BODY() - - // The id of the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString Id; - - // The username of the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString Username; - - // The display name of the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString DisplayName; - - // A URL for an avatar image. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString AvatarUrl; - - // The language expected to be a tag which follows the BCP-47 spec. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString Language; - - // The location set by the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString Location; - - // The timezone set by the user. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString TimeZone; - - // Additional information stored as a JSON object. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString MetaData; - - // The Facebook id in the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString FacebookId; - - // The Google id in the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString GoogleId; - - // The Apple Game Center in of the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString GameCenterId; - - // The Apple Sign In ID in the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString AppleId; - - // The Steam ID in the user's account. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FString SteamId; - - // Indicates whether the user is currently online. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - bool Online = false; - - // Number of related edges to this user (friends). - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - int32 EdgeCount; - - // The UNIX time when the user was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FDateTime CreatedAt; - - // The UNIX time when the user was last updated. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - FDateTime updatedAt; - - FNakamaUser(const FString& JsonString); - FNakamaUser(const TSharedPtr JsonObject); - FNakamaUser(): EdgeCount(0), CreatedAt(0), updatedAt(0) { } - -}; - - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUserList -{ - GENERATED_BODY() - - // List of Users - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|User") - TArray Users; - - FNakamaUserList(const FString& JsonString); - FNakamaUserList() { } -}; diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaUserSession.h b/Nakama/Source/NakamaUnreal/Public/NakamaUserSession.h deleted file mode 100644 index 5e76d2692..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaUserSession.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "NakamaUserSession.generated.h" - -USTRUCT(BlueprintType) -struct NAKAMAUNREAL_API FNakamaUserSession -{ - GENERATED_BODY() - - // The authentication token used to construct this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FString AuthToken; - - // The refresh token used to construct this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FString RefreshToken; - - // True if the user account for this session was just created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - bool IsCreated; - - // The username of the user who owns this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FString Username; - - // The ID of the user who owns this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FString UserId; - - // The timestamp in milliseconds when this session object was created. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FDateTime CreateTime; - - // The timestamp in milliseconds when this session will expire. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FDateTime ExpireTime; - - // The timestamp in milliseconds when this session will expire. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - FDateTime RefreshExpireTime; - - // True if the session has expired against the current time. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - bool IsExpired; - - // True if the session has expired against the current time. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - bool IsRefreshExpired; - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Nakama|Session") - TMap Variables; - - FNakamaUserSession(); // Default Constructor -}; \ No newline at end of file diff --git a/Nakama/Source/NakamaUnreal/Public/NakamaUtils.h b/Nakama/Source/NakamaUnreal/Public/NakamaUtils.h deleted file mode 100644 index 17a5d0ea0..000000000 --- a/Nakama/Source/NakamaUnreal/Public/NakamaUtils.h +++ /dev/null @@ -1,275 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" - -#include "NakamaClient.h" -#include "Templates/SharedPointer.h" -#include "NakamaLogger.h" -#include "Misc/Base64.h" -#include "NakamaLoggingMacros.h" - -class FJsonObject; - -class NAKAMAUNREAL_API FNakamaUtils -{ -public: - // Handle Request Methods for REST API - static FString ENakamaRequesMethodToFString(ENakamaRequestMethod Verb) - { - switch (Verb) - { - case ENakamaRequestMethod::GET: - return TEXT("GET"); - case ENakamaRequestMethod::POST: - return TEXT("POST"); - case ENakamaRequestMethod::PUT: - return TEXT("PUT"); - case ENakamaRequestMethod::DEL: - return TEXT("DELETE"); - default: - // Handle unrecognized verb if needed - break; - } - - // Return an empty string for unrecognized verbs - return FString(); - } - - // Bool to String Helper - static FString BoolToString(bool Value) - { - return Value ? TEXT("true") : TEXT("false"); - } - - // Build Query String - static FString BuildQueryString(const TMultiMap& QueryParams) - { - FString QueryString; - - for (const auto& Param : QueryParams) - { - if (!QueryString.IsEmpty()) - { - QueryString += "&"; - } - - // Only specific inputs needs to be encoded - //FString EncodedKey = FGenericPlatformHttp::UrlEncode(Param.Key); - //FString EncodedValue = FGenericPlatformHttp::UrlEncode(Param.Value); - - QueryString += Param.Key + "=" + Param.Value; - } - - return QueryString; - } - - // Extra client checks for lambdas in requests - static bool IsClientActive(const UNakamaClient *Client) - { - return IsValid(Client) && Client->bIsActive == true; - } - - // Extra client checks for lambdas in requests - static bool IsRealtimeClientActive(const UNakamaRealtimeClient* RealtimeClient) - { - return IsValid(RealtimeClient) && RealtimeClient->bIsActive == true; - } - - // Json helpers - static FString EncodeJson(TSharedPtr JsonObject) - { - FString OutputString; - const TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); - FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); - return OutputString; - } - - static bool SerializeJsonObject(const TSharedPtr& JsonObject, FString& OutSerializedJson) - { - if (!JsonObject.IsValid()) - { - return false; - } - - const TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&OutSerializedJson); - if (!FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter)) - { - JsonWriter->Close(); - return false; - } - - JsonWriter->Close(); - - return true; - } - - static TSharedPtr DeserializeJsonObject(const FString& JsonString) { - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - JsonObject = nullptr; - } - return JsonObject; - } - - static void AddVarsToJson(const TSharedPtr& JsonObject, const TMap& Vars, const FString varsFieldName = TEXT("vars"), const bool addAlways = false) { - - if (addAlways || Vars.Num() > 0) - { - const TSharedPtr VarsJson = MakeShared(); - for (const auto& Var : Vars) - { - if (!Var.Key.IsEmpty() && !Var.Value.IsEmpty()) - { - VarsJson->SetStringField(Var.Key, Var.Value); - } - else - { - NAKAMA_LOG_WARN(TEXT("AddVarsToJson: Empty key or value detected.")); - } - } - JsonObject->SetObjectField(varsFieldName, VarsJson); - } - } - - // Enum as integer string - template - static FString GetEnumValueAsIntString(TEnum EnumValue) - { - const int32 IntValue = static_cast(EnumValue); - return FString::FromInt(IntValue); - } - - // Error handling - static FNakamaError HandleInvalidClient() - { - FNakamaError Error; - Error.Message = FString(TEXT("Client Missing")); - Error.Code = ENakamaErrorCode::InvalidArgument; - - NAKAMA_LOG_ERROR(Error.Message); - - return Error; - } - - static FNakamaError HandleInvalidSession() - { - FNakamaError Error; - Error.Message = FString(TEXT("Session Missing")); - Error.Code = ENakamaErrorCode::InvalidArgument; - - NAKAMA_LOG_ERROR(Error.Message); - - return Error; - } - - static FNakamaError HandleInvalidClientAndSession() - { - FNakamaError Error; - Error.Message = FString(TEXT("Client and Session Missing")); - Error.Code = ENakamaErrorCode::InvalidArgument; - - NAKAMA_LOG_ERROR(Error.Message); - - return Error; - } - - static FNakamaRtError HandleInvalidRealtimeClient() - { - FNakamaRtError Error; - Error.Message = FString(TEXT("Realtime Client Missing")); - Error.Code = ENakamaRtErrorCode::UNKNOWN; // Do bad input? - - NAKAMA_LOG_ERROR(Error.Message); - - return Error; - } - - // Base64 Encode/Decode - static FString Base64Encode(const FString& Source) - { - TArray ByteArray; - FTCHARToUTF8 StringSrc = FTCHARToUTF8(Source.GetCharArray().GetData()); - ByteArray.Append((uint8*)StringSrc.Get(), StringSrc.Length()); - - return FBase64::Encode(ByteArray); - } - - static bool Base64Decode(const FString& Source, FString& Dest) - { - TArray ByteArray; - bool Success = FBase64::Decode(Source, ByteArray); - - FUTF8ToTCHAR StringSrc = FUTF8ToTCHAR((const ANSICHAR*)ByteArray.GetData(), ByteArray.Num()); - Dest.AppendChars(StringSrc.Get(), StringSrc.Length()); - - return Success; - } - - // Working with Optionals (mainly from Blueprints) - - template - static TOptional CreateOptional(const T& value, const T& defaultValue) - { - return value != defaultValue ? TOptional(value) : TOptional(); - } - - // Conversion - - static TMap ConvertFloatMapToDouble(const TMap& FloatMap) - { - TMap DoubleMap; - for (const auto& Pair : FloatMap) - { - DoubleMap.Add(Pair.Key, static_cast(Pair.Value)); - } - return DoubleMap; - } - - // Common functions used by multiple clients - static void ProcessRequestComplete(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, const TFunction& SuccessCallback, const TFunction& ErrorCallback); - static void ProcessRequestCompleteMove(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, const TFunction& SuccessCallback, const TFunction& ErrorCallback); - - static void HandleJsonSerializationFailure(TFunction ErrorCallback); - static bool IsSessionValid(const UNakamaSession* Session, TFunction ErrorCallback); - static bool IsResponseSuccessful(int32 ResponseCode); - static FNakamaError CreateRequestFailureError(); - - // Make HTTP request - static TSharedRef MakeRequest( - const FString& URL, - const FString& Content, - ENakamaRequestMethod RequestMethod, - const FString& SessionToken, - float Timeout - ); - - static void SetBasicAuthorizationHeader(TSharedRef HttpRequest, const FString& ServerKey) - { - FString AuthToken = FString::Printf(TEXT("%s:"), *ServerKey); - FTCHARToUTF8 Utf8Token = FTCHARToUTF8(*AuthToken); - FString EncodedAuthToken = FBase64::Encode((const uint8*)Utf8Token.Get(), Utf8Token.Length()); - FString AuthorizationHeader = FString::Printf(TEXT("Basic %s"), *EncodedAuthToken); - - //NAKAMA_LOG_DEBUG(FString::Printf( TEXT("Authorization Header: %s"), *AuthorizationHeader )); - - HttpRequest->SetHeader(TEXT("Authorization"), AuthorizationHeader); - } -}; diff --git a/NakamaBlueprintsDemo/Config/DefaultEngine.ini b/NakamaBlueprintsDemo/Config/DefaultEngine.ini deleted file mode 100644 index 95a4ae74e..000000000 --- a/NakamaBlueprintsDemo/Config/DefaultEngine.ini +++ /dev/null @@ -1,38 +0,0 @@ - - -[/Script/EngineSettings.GameMapsSettings] -GameDefaultMap=/Game/NakamaDemo/Maps/DemoMap.DemoMap -EditorStartupMap=/Game/NakamaDemo/Maps/DemoMap.DemoMap -GameInstanceClass=/Script/Engine.GameInstance - -[/Script/HardwareTargeting.HardwareTargetingSettings] -TargetedHardwareClass=Desktop -AppliedTargetedHardwareClass=Desktop -DefaultGraphicsPerformance=Maximum -AppliedDefaultGraphicsPerformance=Maximum - -[/Script/Engine.Engine] -+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/NakamaBlueprintsDemo") -+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/NakamaBlueprintsDemo") -+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="NakamaBlueprintsDemoGameModeBase") - -[/Script/Engine.NetworkSettings] -n.VerifyPeer=False - -[/Script/Engine.GarbageCollectionSettings] -gc.MultithreadedDestructionEnabled=True - -[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] -bEnablePlugin=True -bAllowNetworkConnection=True -SecurityToken=F8E278C148873F2AC8FEF6AEBFC08E8E -bIncludeInShipping=False -bAllowExternalStartInShipping=False -bCompileAFSProject=False -bUseCompression=False -bLogFiles=False -bReportStats=False -ConnectionType=USBOnly -bUseManualIPAddress=False -ManualIPAddress= - diff --git a/NakamaBlueprintsDemo/Config/DefaultGame.ini b/NakamaBlueprintsDemo/Config/DefaultGame.ini deleted file mode 100644 index 9f6554d41..000000000 --- a/NakamaBlueprintsDemo/Config/DefaultGame.ini +++ /dev/null @@ -1,95 +0,0 @@ - - -[/Script/EngineSettings.GeneralProjectSettings] -ProjectID=1F3E7F814B6C60C48C607BBBE3392E9A - -[/Script/UnrealEd.ProjectPackagingSettings] -Build=IfProjectHasCode -BuildConfiguration=PPBC_Development -BuildTarget= -FullRebuild=False -ForDistribution=False -IncludeDebugFiles=False -BlueprintNativizationMethod=Disabled -bIncludeNativizedAssetsInProjectGeneration=False -bExcludeMonolithicEngineHeadersInNativizedCode=False -UsePakFile=True -bUseIoStore=True -bUseZenStore=False -bMakeBinaryConfig=False -bGenerateChunks=False -bGenerateNoChunks=False -bChunkHardReferencesOnly=False -bForceOneChunkPerFile=False -MaxChunkSize=0 -bBuildHttpChunkInstallData=False -HttpChunkInstallDataDirectory=(Path="") -WriteBackMetadataToAssetRegistry=Disabled -bCompressed=True -PackageCompressionFormat=Oodle -bForceUseProjectCompressionFormatIgnoreHardwareOverride=False -PackageAdditionalCompressionOptions= -PackageCompressionMethod=Kraken -PackageCompressionLevel_DebugDevelopment=4 -PackageCompressionLevel_TestShipping=5 -PackageCompressionLevel_Distribution=7 -PackageCompressionMinBytesSaved=1024 -PackageCompressionMinPercentSaved=5 -bPackageCompressionEnableDDC=False -PackageCompressionMinSizeToConsiderDDC=0 -HttpChunkInstallDataVersion= -IncludePrerequisites=True -IncludeAppLocalPrerequisites=False -bShareMaterialShaderCode=True -bDeterministicShaderCodeOrder=False -bSharedMaterialNativeLibraries=True -ApplocalPrerequisitesDirectory=(Path="") -IncludeCrashReporter=False -InternationalizationPreset=English --CulturesToStage=en -+CulturesToStage=en -LocalizationTargetCatchAllChunkId=0 -bCookAll=False -bCookMapsOnly=False -bSkipEditorContent=False -bSkipMovies=False --IniKeyDenylist=KeyStorePassword --IniKeyDenylist=KeyPassword --IniKeyDenylist=rsa.privateexp --IniKeyDenylist=rsa.modulus --IniKeyDenylist=rsa.publicexp --IniKeyDenylist=aes.key --IniKeyDenylist=SigningPublicExponent --IniKeyDenylist=SigningModulus --IniKeyDenylist=SigningPrivateExponent --IniKeyDenylist=EncryptionKey --IniKeyDenylist=DevCenterUsername --IniKeyDenylist=DevCenterPassword --IniKeyDenylist=IOSTeamID --IniKeyDenylist=SigningCertificate --IniKeyDenylist=MobileProvision --IniKeyDenylist=IniKeyDenylist --IniKeyDenylist=IniSectionDenylist -+IniKeyDenylist=KeyStorePassword -+IniKeyDenylist=KeyPassword -+IniKeyDenylist=rsa.privateexp -+IniKeyDenylist=rsa.modulus -+IniKeyDenylist=rsa.publicexp -+IniKeyDenylist=aes.key -+IniKeyDenylist=SigningPublicExponent -+IniKeyDenylist=SigningModulus -+IniKeyDenylist=SigningPrivateExponent -+IniKeyDenylist=EncryptionKey -+IniKeyDenylist=DevCenterUsername -+IniKeyDenylist=DevCenterPassword -+IniKeyDenylist=IOSTeamID -+IniKeyDenylist=SigningCertificate -+IniKeyDenylist=MobileProvision -+IniKeyDenylist=IniKeyDenylist -+IniKeyDenylist=IniSectionDenylist --IniSectionDenylist=HordeStorageServers --IniSectionDenylist=StorageServers -+IniSectionDenylist=HordeStorageServers -+IniSectionDenylist=StorageServers -+MapsToCook=(FilePath="/Game/NakamaDemo/Maps/DemoMap") - diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Backgrounds/Background.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Backgrounds/Background.uasset deleted file mode 100644 index b84aff52b..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Backgrounds/Background.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Button.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Button.uasset deleted file mode 100644 index 4037ff90b..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Button.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Card.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Card.uasset deleted file mode 100644 index fc6db369d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/Card.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/crown.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/crown.uasset deleted file mode 100644 index 507f27fec..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Components/crown.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/GitHub.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/GitHub.uasset deleted file mode 100644 index ebd814030..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/GitHub.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/blog.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/blog.uasset deleted file mode 100644 index e16415a9e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/blog.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/cloud.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/cloud.uasset deleted file mode 100644 index bf8d07a69..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/cloud.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/forums.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/forums.uasset deleted file mode 100644 index b99f9aa92..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/forums.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/google-docs.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/google-docs.uasset deleted file mode 100644 index 5d6198ee6..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/google-docs.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/marketplace.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/marketplace.uasset deleted file mode 100644 index 3081ed51e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/marketplace.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/settings.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/settings.uasset deleted file mode 100644 index 96a444efa..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/settings.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/support.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/support.uasset deleted file mode 100644 index 5e9d315a7..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Icons/support.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/1.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/1.uasset deleted file mode 100644 index d359881ec..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/1.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2.uasset deleted file mode 100644 index 8e39efca9..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_medium.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_medium.uasset deleted file mode 100644 index 470dd519c..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_medium.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_small.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_small.uasset deleted file mode 100644 index df1f33269..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/2_small.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/3.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/3.uasset deleted file mode 100644 index 1ca9320be..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/Logos/3.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-dark-text.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-dark-text.uasset deleted file mode 100644 index 92c96b28f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-dark-text.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text-small.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text-small.uasset deleted file mode 100644 index f12322efd..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text-small.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text.uasset deleted file mode 100644 index 23668fc73..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo-white-text.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo.uasset deleted file mode 100644 index a74e33dda..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-logo.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-text.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-text.uasset deleted file mode 100644 index 87cfcfd52..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/dark-text.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text-small.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text-small.uasset deleted file mode 100644 index 30c563ae3..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text-small.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text.uasset deleted file mode 100644 index d61369a79..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo-white-text.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo.uasset deleted file mode 100644 index 4a0069027..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-logo.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-text.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-text.uasset deleted file mode 100644 index 90e224caa..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Design/NewLogos/white-text.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Black.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Black.uasset deleted file mode 100644 index 1a89e55be..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Black.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BlackItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BlackItalic.uasset deleted file mode 100644 index 0236bb39d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BlackItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Bold.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Bold.uasset deleted file mode 100644 index 479b51b48..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Bold.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BoldItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BoldItalic.uasset deleted file mode 100644 index 1f14fbd4c..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-BoldItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBold.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBold.uasset deleted file mode 100644 index 1cb10217f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBold.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBoldItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBoldItalic.uasset deleted file mode 100644 index 155f839be..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraBoldItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLight.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLight.uasset deleted file mode 100644 index 807e3e331..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLight.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLightItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLightItalic.uasset deleted file mode 100644 index 8868cd71a..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ExtraLightItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Italic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Italic.uasset deleted file mode 100644 index e7c2d29a2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Italic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Light.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Light.uasset deleted file mode 100644 index 561b08d9e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Light.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-LightItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-LightItalic.uasset deleted file mode 100644 index de5d8c73d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-LightItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Medium.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Medium.uasset deleted file mode 100644 index 117d53393..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Medium.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-MediumItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-MediumItalic.uasset deleted file mode 100644 index 92b5ea3c8..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-MediumItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular.uasset deleted file mode 100644 index 1f6285d03..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular_Font.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular_Font.uasset deleted file mode 100644 index 2e0a152df..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Regular_Font.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBold.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBold.uasset deleted file mode 100644 index 91b99994e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBold.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBoldItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBoldItalic.uasset deleted file mode 100644 index beba9093d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-SemiBoldItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Thin.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Thin.uasset deleted file mode 100644 index ba73d912e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-Thin.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ThinItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ThinItalic.uasset deleted file mode 100644 index ebc2ff7ef..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/Montserrat/Montserrat-ThinItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Black.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Black.uasset deleted file mode 100644 index 690c55314..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Black.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BlackItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BlackItalic.uasset deleted file mode 100644 index 41653b78f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BlackItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Bold.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Bold.uasset deleted file mode 100644 index e4f6edb17..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Bold.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BoldItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BoldItalic.uasset deleted file mode 100644 index cb548cd44..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-BoldItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLight.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLight.uasset deleted file mode 100644 index 376f4b678..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLight.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.uasset deleted file mode 100644 index 85de30103..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Italic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Italic.uasset deleted file mode 100644 index 0f1c2d977..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Italic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Light.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Light.uasset deleted file mode 100644 index 310cc03c6..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Light.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-LightItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-LightItalic.uasset deleted file mode 100644 index 08d5aa84d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-LightItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular.uasset deleted file mode 100644 index 1274c25fb..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular_Font.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular_Font.uasset deleted file mode 100644 index 4503b33d0..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-Regular_Font.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBold.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBold.uasset deleted file mode 100644 index da19131b8..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBold.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.uasset deleted file mode 100644 index be422cce3..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Assets/Fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPI_Nakama.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPI_Nakama.uasset deleted file mode 100644 index 85305411b..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPI_Nakama.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPL_Nakama.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPL_Nakama.uasset deleted file mode 100644 index 8097ac5b1..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/BPL_Nakama.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/GM_NakamaDemo.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/GM_NakamaDemo.uasset deleted file mode 100644 index 3cb1a8c5b..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/GM_NakamaDemo.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/NakamaDemoController.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/NakamaDemoController.uasset deleted file mode 100644 index a1922d0b5..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Core/NakamaDemoController.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ELoginTypes.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ELoginTypes.uasset deleted file mode 100644 index f03afc9b2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ELoginTypes.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ENotificationSeverity.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ENotificationSeverity.uasset deleted file mode 100644 index afe3289a2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Blueprints/Enums/ENotificationSeverity.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Maps/DemoMap.umap b/NakamaBlueprintsDemo/Content/NakamaDemo/Maps/DemoMap.umap deleted file mode 100644 index 3d5e3d0e9..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Maps/DemoMap.umap and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button.uasset deleted file mode 100644 index 26bb54580..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button_TextOnly.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button_TextOnly.uasset deleted file mode 100644 index bfce4c947..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Button_TextOnly.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatContent.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatContent.uasset deleted file mode 100644 index 9cb018358..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatContent.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatGroup.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatGroup.uasset deleted file mode 100644 index 081de821f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatGroup.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatMessage.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatMessage.uasset deleted file mode 100644 index 1a011a50f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ChatMessage.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DockItem.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DockItem.uasset deleted file mode 100644 index 70b8d5757..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DockItem.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DropDown.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DropDown.uasset deleted file mode 100644 index d4f2963a9..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_DropDown.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ErrorMessage.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ErrorMessage.uasset deleted file mode 100644 index 2e664f720..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ErrorMessage.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Friend.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Friend.uasset deleted file mode 100644 index ae6b0f8fd..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Friend.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_GroupUser.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_GroupUser.uasset deleted file mode 100644 index 6fbc11a54..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_GroupUser.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_InputField.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_InputField.uasset deleted file mode 100644 index 233963b5d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_InputField.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_LeaderboardRecord.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_LeaderboardRecord.uasset deleted file mode 100644 index b77dfd3ec..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_LeaderboardRecord.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ListedGroup.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ListedGroup.uasset deleted file mode 100644 index dac87a9cb..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_ListedGroup.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Match.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Match.uasset deleted file mode 100644 index f6d9844a1..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Match.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchContent.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchContent.uasset deleted file mode 100644 index fb30e8cf7..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchContent.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchData.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchData.uasset deleted file mode 100644 index 85576dc4c..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchData.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchPresence.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchPresence.uasset deleted file mode 100644 index b0c1d6514..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchPresence.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Matched.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Matched.uasset deleted file mode 100644 index b9e7686aa..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Matched.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchmakerTicket.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchmakerTicket.uasset deleted file mode 100644 index f4cc9f603..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_MatchmakerTicket.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Notification.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Notification.uasset deleted file mode 100644 index b0c4b8748..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Notification.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NotificationTray.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NotificationTray.uasset deleted file mode 100644 index 401c5058a..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NotificationTray.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NumberSelector.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NumberSelector.uasset deleted file mode 100644 index 05a02b3f2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_NumberSelector.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartiesContent.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartiesContent.uasset deleted file mode 100644 index e5f8c65bd..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartiesContent.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Party.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Party.uasset deleted file mode 100644 index 95c46a8c5..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Party.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyJoinRequest.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyJoinRequest.uasset deleted file mode 100644 index 06c447cb7..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyJoinRequest.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyUserPresence.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyUserPresence.uasset deleted file mode 100644 index d88012faa..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_PartyUserPresence.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_StorageObject.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_StorageObject.uasset deleted file mode 100644 index 660f366b6..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_StorageObject.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Tournament.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Tournament.uasset deleted file mode 100644 index 3476707f0..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_Tournament.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UpdateGroup.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UpdateGroup.uasset deleted file mode 100644 index 5e00e36a4..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UpdateGroup.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserGroup.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserGroup.uasset deleted file mode 100644 index c06b6914e..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserGroup.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserPresence.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserPresence.uasset deleted file mode 100644 index 54470d75f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Components/WBP_UserPresence.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Account.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Account.uasset deleted file mode 100644 index 55656fd3d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Account.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_ChatPanel.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_ChatPanel.uasset deleted file mode 100644 index b654352f2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_ChatPanel.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Friends.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Friends.uasset deleted file mode 100644 index 5a3070403..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Friends.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Groups.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Groups.uasset deleted file mode 100644 index 3d44a308c..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Groups.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Leaderboards.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Leaderboards.uasset deleted file mode 100644 index 94f7885b8..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Leaderboards.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Matchmaking.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Matchmaking.uasset deleted file mode 100644 index 46280f094..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Matchmaking.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Parties.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Parties.uasset deleted file mode 100644 index 542ab40d2..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Parties.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Realtime.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Realtime.uasset deleted file mode 100644 index ea7182021..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Realtime.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Storage.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Storage.uasset deleted file mode 100644 index a5ba4c04a..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Storage.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Tournaments.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Tournaments.uasset deleted file mode 100644 index 9fe4dffbb..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/Panels/WBP_Tournaments.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Login.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Login.uasset deleted file mode 100644 index bac48582d..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Login.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Main.uasset b/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Main.uasset deleted file mode 100644 index 68d92492f..000000000 Binary files a/NakamaBlueprintsDemo/Content/NakamaDemo/Widgets/WBP_Main.uasset and /dev/null differ diff --git a/NakamaBlueprintsDemo/NakamaBlueprintsDemo.uproject b/NakamaBlueprintsDemo/NakamaBlueprintsDemo.uproject deleted file mode 100644 index c92ade017..000000000 --- a/NakamaBlueprintsDemo/NakamaBlueprintsDemo.uproject +++ /dev/null @@ -1,19 +0,0 @@ -{ - "FileVersion": 3, - "EngineAssociation": "5.2", - "Category": "", - "Description": "", - "Modules": [ - { - "Name": "NakamaBlueprintsDemo", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ], - "Plugins": [ - { - "Name": "Nakama", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/NakamaBlueprintsDemo/README.md b/NakamaBlueprintsDemo/README.md deleted file mode 100644 index 9e5111a32..000000000 --- a/NakamaBlueprintsDemo/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Nakama Demo Project -This is an example Unreal Engine project showcasing the core features of Nakama, made using only the plugins and 100% Blueprints. - -This documentation is NOT Final and subject to change. - -This repository includes the Nakama plugin within the `Plugins` folder. - -It is primarily tested on Unreal Engine 5.0 - -# Getting Started -These are the steps to get started with the project. - -1. Install and run the Nakama server. Follow these [instructions](https://heroiclabs.com/docs/install-docker-quickstart). -2. Install [Unreal Engine](https://www.unrealengine.com) 5.0 or greater -3. Right click the .uproject and select `Generate Visual Studio Project Files` -4. Open the solution in your Unreal Compatible IDE (Visual Studio, Xcode, Rider) and run the project -5. Once the Unreal Editor is open, click Play and start testing the features -6. If you have different server credentials than the default ones, or are running a remote server, open the `NakamaDemoController` and replace the server parameters in the `Create Default Client` and `Setup Realtime Client` which are highlighted in green. - -# Build and Test -Follow these instructions to package the example project: - -https://docs.unrealengine.com/5.0/en-US/packaging-unreal-engine-projects/ - -To package it from the command line on Windows run: - -```& "${env:UnrealEngine}\Engine\Build\BatchFiles\RunUAT.bat" BuildCookRun -Platform=Win64 -Project="${env:NakamaUnreal}\NakamaBlueprintsDemo\NakamaBlueprintsDemo.uproject" -ClientConfig=Development -Cook -Build``` - -On Mac: - -```"$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh" BuildCookRun -Platform=Mac -Project="${NAKAMA_UNREAL}/NakamaBlueprintsDemo/NakamaBlueprintsDemo.uproject" -ClientConfig=Development -Cook -Build -Architecture_Mac=arm64``` \ No newline at end of file diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo.Target.cs b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo.Target.cs deleted file mode 100644 index d245ced88..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo.Target.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaBlueprintsDemoTarget : TargetRules -{ - public NakamaBlueprintsDemoTarget( TargetInfo Target) : base(Target) - { - Type = TargetType.Game; - DefaultBuildSettings = BuildSettingsVersion.V2; - ExtraModuleNames.AddRange( new string[] { "NakamaBlueprintsDemo" } ); - } -} diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.Build.cs b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.Build.cs deleted file mode 100644 index ef2a9fe3e..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.Build.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; - -public class NakamaBlueprintsDemo : ModuleRules -{ - public NakamaBlueprintsDemo(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); - - PrivateDependencyModuleNames.AddRange(new string[] { "NakamaBlueprints" }); - - } -} diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.cpp b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.cpp deleted file mode 100644 index 274b1cb6e..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "NakamaBlueprintsDemo.h" -#include "Modules/ModuleManager.h" - -IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, NakamaBlueprintsDemo, "NakamaBlueprintsDemo" ); diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.h b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.h deleted file mode 100644 index 677c8e25b..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemo.h +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" - diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.cpp b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.cpp deleted file mode 100644 index f319d15be..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - - -#include "NakamaBlueprintsDemoGameModeBase.h" - diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.h b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.h deleted file mode 100644 index a4d8f0f8c..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemo/NakamaBlueprintsDemoGameModeBase.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/GameModeBase.h" -#include "NakamaBlueprintsDemoGameModeBase.generated.h" - -/** - * - */ -UCLASS() -class NAKAMABLUEPRINTSDEMO_API ANakamaBlueprintsDemoGameModeBase : public AGameModeBase -{ - GENERATED_BODY() - -}; diff --git a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemoEditor.Target.cs b/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemoEditor.Target.cs deleted file mode 100644 index cc59b8ec2..000000000 --- a/NakamaBlueprintsDemo/Source/NakamaBlueprintsDemoEditor.Target.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaBlueprintsDemoEditorTarget : TargetRules -{ - public NakamaBlueprintsDemoEditorTarget( TargetInfo Target) : base(Target) - { - Type = TargetType.Editor; - DefaultBuildSettings = BuildSettingsVersion.V2; - ExtraModuleNames.AddRange( new string[] { "NakamaBlueprintsDemo" } ); - } -} diff --git a/NakamaBlueprintsTest/Config/Automation/Presets/NakamaAndSator.json b/NakamaBlueprintsTest/Config/Automation/Presets/NakamaAndSator.json deleted file mode 100644 index 06b2e954a..000000000 --- a/NakamaBlueprintsTest/Config/Automation/Presets/NakamaAndSator.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "id": "NakamaAndSator", - "name": "NakamaAndSatori", - "enabledTests": [ - "Nakama.Base.Authenticate.Device", - "Nakama.Base.Authenticate.Device2", - "Nakama.Base.Authenticate.Email", - "Nakama.Base.Authenticate.Email2", - "Nakama.Base.Errors.InvalidArgument", - "Nakama.Base.Errors.InvalidArgument2", - "Nakama.Base.Friends.ListFriends", - "Nakama.Base.Groups.List", - "Nakama.Base.Internals.UriEncode", - "Nakama.Base.Realtime..FollowUsers", - "Nakama.Base.Realtime.Chat.JoinChat", - "Nakama.Base.Realtime.Chat.JoinChatWriteMessage", - "Nakama.Base.Realtime.Chat.JoinGroupChat", - "Nakama.Base.Realtime.Matches.AddMatchmaker", - "Nakama.Base.Realtime.Matches.AuthoritativeMatch", - "Nakama.Base.Realtime.Matches.Match", - "Nakama.Base.Realtime.Matches.MatchmakerJoinMatch", - "Nakama.Base.Realtime.Notifications.CreateListDelete", - "Nakama.Base.Realtime.Notifications.CreateReceive", - "Nakama.Base.Realtime.Parties.CreateParty", - "Nakama.Base.Realtime.Parties.ListPartyJoinRequests", - "Nakama.Base.Realtime.Parties.PartyMatchmaker", - "Nakama.Base.Realtime.Parties.ReceivedPartyJoinRequests", - "Nakama.Base.Realtime.RPC.RPCWithAuth1", - "Nakama.Base.Realtime.RPC.RPCWithAuth2", - "Nakama.Base.Realtime.RPC.RPCWithAuth4", - "Nakama.Base.Realtime.RPC.RPCWithAuth5", - "Nakama.Base.Realtime.RPC.RPCWithHttpKey", - "Nakama.Base.Realtime.RPC.RPCWithHttpKey2", - "Nakama.Base.Realtime.Tournament", - "Nakama.Base.Sessions.RestoreSession", - "Nakama.Base.Sessions.RestoreSession2", - "Nakama.Base.Storage.InvalidArgument", - "Nakama.Base.Storage.Write", - "Nakama.Base.Storage.WriteCursor", - "Nakama.Base.Users.GetAccount", - "Nakama.Base.Users.GetUsers", - "Project.Functional Tests.FTEST_Nakama.Test_Authenticate_Device", - "Project.Functional Tests.FTEST_Nakama.Test_Authenticate_Device_2", - "Project.Functional Tests.FTEST_Nakama.Test_Authenticate_Email", - "Project.Functional Tests.FTEST_Nakama.Test_Error_InvalidArgument", - "Project.Functional Tests.FTEST_Nakama.Test_Error_InvalidArgument_2", - "Project.Functional Tests.FTEST_Nakama.Test_Friends_ListFriends", - "Project.Functional Tests.FTEST_Nakama.Test_Groups_List", - "Project.Functional Tests.FTEST_Nakama.Test_Sessions_RestoreSession", - "Project.Functional Tests.FTEST_Nakama.Test_Sessions_RestoreSession_2", - "Project.Functional Tests.FTEST_Nakama.Test_Storage_InvalidArgument", - "Project.Functional Tests.FTEST_Nakama.Test_Storage_Write", - "Project.Functional Tests.FTEST_Nakama.Test_Storage_Write_Cursor", - "Project.Functional Tests.FTEST_Nakama.Test_Tournament3", - "Project.Functional Tests.FTEST_Nakama.Test_Users_GetUserAccount", - "Project.Functional Tests.FTEST_Nakama.Test_Users_GetUsers", - "Project.Functional Tests.FTEST_Nakama_Realtime.Tes_Realtime_Notifications_CreateListDelete", - "Project.Functional Tests.FTEST_Nakama_Realtime.Tes_Realtime_Notifications_CreateReceive", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_AddMatchmaker", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_AuthoritativeMatch", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Chat_JoinChat", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Chat_JoinChat_WriteMessage", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Chat_JoinGroupChat", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Match", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_MatchmakerJoinMatch", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Parties_CreateJoin", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Parties_Matchmaker", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithAuth1", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithAuth2", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithAuth4", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithAuth5", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithHttpKey", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_RPC_WithHttpKey2", - "Project.Functional Tests.FTEST_Nakama_Realtime.Test_Realtime_Tournament", - "Project.Functional Tests.FTEST_Satori.Test_Authenticate", - "Project.Functional Tests.FTEST_Satori.Test_PostEvent" - ] -} \ No newline at end of file diff --git a/NakamaBlueprintsTest/Config/DefaultEditor.ini b/NakamaBlueprintsTest/Config/DefaultEditor.ini deleted file mode 100644 index e69de29bb..000000000 diff --git a/NakamaBlueprintsTest/Config/DefaultEngine.ini b/NakamaBlueprintsTest/Config/DefaultEngine.ini deleted file mode 100644 index 4b1152413..000000000 --- a/NakamaBlueprintsTest/Config/DefaultEngine.ini +++ /dev/null @@ -1,73 +0,0 @@ - - -[/Script/EngineSettings.GameMapsSettings] -GameDefaultMap=/Game/FTEST_Nakama.FTEST_Nakama -EditorStartupMap=/Game/FTEST_Nakama.FTEST_Nakama - -[/Script/WindowsTargetPlatform.WindowsTargetSettings] -DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 --D3D12TargetedShaderFormats=PCD3D_SM5 -+D3D12TargetedShaderFormats=PCD3D_SM6 --D3D11TargetedShaderFormats=PCD3D_SM5 -+D3D11TargetedShaderFormats=PCD3D_SM5 -Compiler=Default -AudioSampleRate=48000 -AudioCallbackBufferFrameSize=1024 -AudioNumBuffersToEnqueue=1 -AudioMaxChannels=0 -AudioNumSourceWorkers=4 -SpatializationPlugin= -SourceDataOverridePlugin= -ReverbPlugin= -OcclusionPlugin= -CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) -CacheSizeKB=65536 -MaxChunkSizeOverrideKB=0 -bResampleForDevice=False -MaxSampleRate=48000.000000 -HighSampleRate=32000.000000 -MedSampleRate=24000.000000 -LowSampleRate=12000.000000 -MinSampleRate=8000.000000 -CompressionQualityModifier=1.000000 -AutoStreamingThreshold=0.000000 -SoundCueCookQualityIndex=-1 - - -[/Script/HardwareTargeting.HardwareTargetingSettings] -TargetedHardwareClass=Desktop -AppliedTargetedHardwareClass=Desktop -DefaultGraphicsPerformance=Maximum -AppliedDefaultGraphicsPerformance=Maximum - -[/Script/Engine.RendererSettings] -r.GenerateMeshDistanceFields=True -r.DynamicGlobalIlluminationMethod=1 -r.ReflectionMethod=1 -r.Shadow.Virtual.Enable=1 -r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True - -[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] -CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' - -[/Script/Engine.UserInterfaceSettings] -bAuthorizeAutomaticWidgetVariableCreation=False - -[/Script/Engine.Engine] -+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/NakamaBlueprintsTest") -+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/NakamaBlueprintsTest") - -[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] -bEnablePlugin=True -bAllowNetworkConnection=True -SecurityToken=8A39C8BF4DF34CC560C1F38D15E567B9 -bIncludeInShipping=False -bAllowExternalStartInShipping=False -bCompileAFSProject=False -bUseCompression=False -bLogFiles=False -bReportStats=False -ConnectionType=USBOnly -bUseManualIPAddress=False -ManualIPAddress= - diff --git a/NakamaBlueprintsTest/Config/DefaultGame.ini b/NakamaBlueprintsTest/Config/DefaultGame.ini deleted file mode 100644 index 947d75fea..000000000 --- a/NakamaBlueprintsTest/Config/DefaultGame.ini +++ /dev/null @@ -1,4 +0,0 @@ - - -[/Script/EngineSettings.GeneralProjectSettings] -ProjectID=7892922444584D0E4DDF4F820184F440 diff --git a/NakamaBlueprintsTest/Config/DefaultInput.ini b/NakamaBlueprintsTest/Config/DefaultInput.ini deleted file mode 100644 index 4cc36056b..000000000 --- a/NakamaBlueprintsTest/Config/DefaultInput.ini +++ /dev/null @@ -1,85 +0,0 @@ -[/Script/Engine.InputSettings] --AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) --AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) --AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) -+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -bAltEnterTogglesFullscreen=True -bF11TogglesFullscreen=True -bUseMouseForTouch=False -bEnableMouseSmoothing=True -bEnableFOVScaling=True -bCaptureMouseOnLaunch=True -bEnableLegacyInputScales=True -bEnableMotionControls=True -bFilterInputByPlatformUser=False -bShouldFlushPressedKeysOnViewportFocusLost=True -bAlwaysShowTouchInterface=False -bShowConsoleOnFourFingerTap=True -bEnableGestureRecognizer=False -bUseAutocorrect=False -DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown -DefaultViewportMouseLockMode=LockOnCapture -FOVScale=0.011110 -DoubleClickTime=0.200000 -DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput -DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent -DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks --ConsoleKeys=Tilde -+ConsoleKeys=Tilde -+ConsoleKeys=Caret - diff --git a/NakamaBlueprintsTest/Content/FTEST_Nakama.umap b/NakamaBlueprintsTest/Content/FTEST_Nakama.umap deleted file mode 100644 index c0d067fb6..000000000 Binary files a/NakamaBlueprintsTest/Content/FTEST_Nakama.umap and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/FTEST_Nakama_Realtime.umap b/NakamaBlueprintsTest/Content/FTEST_Nakama_Realtime.umap deleted file mode 100644 index 3d536256a..000000000 Binary files a/NakamaBlueprintsTest/Content/FTEST_Nakama_Realtime.umap and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/FTEST_Satori.umap b/NakamaBlueprintsTest/Content/FTEST_Satori.umap deleted file mode 100644 index 1834ba73d..000000000 Binary files a/NakamaBlueprintsTest/Content/FTEST_Satori.umap and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Satori_Tests/Authenticate/Test_Authenticate.uasset b/NakamaBlueprintsTest/Content/Satori_Tests/Authenticate/Test_Authenticate.uasset deleted file mode 100644 index 2684d56f2..000000000 Binary files a/NakamaBlueprintsTest/Content/Satori_Tests/Authenticate/Test_Authenticate.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Satori_Tests/Base/Satori_Test_Base.uasset b/NakamaBlueprintsTest/Content/Satori_Tests/Base/Satori_Test_Base.uasset deleted file mode 100644 index c965ba3c0..000000000 Binary files a/NakamaBlueprintsTest/Content/Satori_Tests/Base/Satori_Test_Base.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Satori_Tests/Event/Test_PostEvent.uasset b/NakamaBlueprintsTest/Content/Satori_Tests/Event/Test_PostEvent.uasset deleted file mode 100644 index 51daed03e..000000000 Binary files a/NakamaBlueprintsTest/Content/Satori_Tests/Event/Test_PostEvent.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Satori_Tests/Test_Authenticate.uasset b/NakamaBlueprintsTest/Content/Satori_Tests/Test_Authenticate.uasset deleted file mode 100644 index a32060111..000000000 Binary files a/NakamaBlueprintsTest/Content/Satori_Tests/Test_Authenticate.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device.uasset b/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device.uasset deleted file mode 100644 index 6fcfa6173..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device_2.uasset b/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device_2.uasset deleted file mode 100644 index 392acb6f0..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Device_2.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Email.uasset b/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Email.uasset deleted file mode 100644 index d484e2701..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Authenticate/Test_Authenticate_Email.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Base/Nakama_Test_Base.uasset b/NakamaBlueprintsTest/Content/Tests/Base/Nakama_Test_Base.uasset deleted file mode 100644 index c26f65eb4..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Base/Nakama_Test_Base.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument.uasset b/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument.uasset deleted file mode 100644 index 808596b16..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument_2.uasset b/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument_2.uasset deleted file mode 100644 index ec879f157..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_InvalidArgument_2.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_NotFound.uasset b/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_NotFound.uasset deleted file mode 100644 index cc0cc3e65..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_NotFound.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_Unauthenticated.uasset b/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_Unauthenticated.uasset deleted file mode 100644 index e74ca8a01..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Errors/Test_Error_Unauthenticated.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Friends/Test_Friends_ListFriends.uasset b/NakamaBlueprintsTest/Content/Tests/Friends/Test_Friends_ListFriends.uasset deleted file mode 100644 index 68eaac731..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Friends/Test_Friends_ListFriends.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Groups/Test_Groups_List.uasset b/NakamaBlueprintsTest/Content/Tests/Groups/Test_Groups_List.uasset deleted file mode 100644 index 6406b53fa..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Groups/Test_Groups_List.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Matches/Test_Matches_ListMatches.uasset b/NakamaBlueprintsTest/Content/Tests/Matches/Test_Matches_ListMatches.uasset deleted file mode 100644 index 4094ce08e..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Matches/Test_Matches_ListMatches.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat.uasset deleted file mode 100644 index ddd3e57ea..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat_WriteMessage.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat_WriteMessage.uasset deleted file mode 100644 index 7deaa664b..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinChat_WriteMessage.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinGroupChat.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinGroupChat.uasset deleted file mode 100644 index e3101f4a9..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Chat/Test_Realtime_Chat_JoinGroupChat.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AddMatchmaker.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AddMatchmaker.uasset deleted file mode 100644 index 5b22359f2..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AddMatchmaker.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AuthoritativeMatch.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AuthoritativeMatch.uasset deleted file mode 100644 index 34687288c..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_AuthoritativeMatch.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_Match.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_Match.uasset deleted file mode 100644 index a697ad4b7..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_Match.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_MatchmakerJoinMatch.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_MatchmakerJoinMatch.uasset deleted file mode 100644 index aefafc930..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Matches/Test_Realtime_MatchmakerJoinMatch.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateListDelete.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateListDelete.uasset deleted file mode 100644 index f22848234..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateListDelete.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateReceive.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateReceive.uasset deleted file mode 100644 index 663c2a204..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Notifications/Tes_Realtime_Notifications_CreateReceive.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Create.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Create.uasset deleted file mode 100644 index 1ce5c1634..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Create.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_CreateJoin.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_CreateJoin.uasset deleted file mode 100644 index 4d4151fbd..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_CreateJoin.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Matchmaker.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Matchmaker.uasset deleted file mode 100644 index bf901a6e5..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Parties/Test_Realtime_Parties_Matchmaker.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth1.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth1.uasset deleted file mode 100644 index 309ded93d..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth1.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth2.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth2.uasset deleted file mode 100644 index a65cce27e..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth2.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth4.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth4.uasset deleted file mode 100644 index f86e7f7f7..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth4.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth5.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth5.uasset deleted file mode 100644 index 9782add27..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithAuth5.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey.uasset deleted file mode 100644 index 70f9fa82e..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey2.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey2.uasset deleted file mode 100644 index 196bf0604..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/RPC/Test_Realtime_RPC_WithHttpKey2.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Realtime_Tournament.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Realtime_Tournament.uasset deleted file mode 100644 index c9b935094..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Realtime_Tournament.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Tournament.uasset b/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Tournament.uasset deleted file mode 100644 index 73b120d2b..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Realtime/Test_Tournament.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession.uasset b/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession.uasset deleted file mode 100644 index d056af115..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession_2.uasset b/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession_2.uasset deleted file mode 100644 index c061f50fa..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Sessions/Test_Sessions_RestoreSession_2.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_InvalidArgument.uasset b/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_InvalidArgument.uasset deleted file mode 100644 index f65d378d8..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_InvalidArgument.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write.uasset b/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write.uasset deleted file mode 100644 index 75f5b6f9b..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write_Cursor.uasset b/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write_Cursor.uasset deleted file mode 100644 index 2b2d9c410..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Storage/Test_Storage_Write_Cursor.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetAccount.uasset b/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetAccount.uasset deleted file mode 100644 index c5f44f8d2..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetAccount.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUserAccount.uasset b/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUserAccount.uasset deleted file mode 100644 index 9fbfb9e6e..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUserAccount.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUsers.uasset b/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUsers.uasset deleted file mode 100644 index e06afe99e..000000000 Binary files a/NakamaBlueprintsTest/Content/Tests/Users/Test_Users_GetUsers.uasset and /dev/null differ diff --git a/NakamaBlueprintsTest/NakamaBlueprintsTest.uproject b/NakamaBlueprintsTest/NakamaBlueprintsTest.uproject deleted file mode 100644 index 3d0f50969..000000000 --- a/NakamaBlueprintsTest/NakamaBlueprintsTest.uproject +++ /dev/null @@ -1,37 +0,0 @@ -{ - "FileVersion": 3, - "EngineAssociation": "{2C8C59F9-4278-49EA-779F-E1A43E90E045}", - "Category": "", - "Description": "", - "Modules": [ - { - "Name": "NakamaBlueprintsTest", - "Type": "Runtime", - "LoadingPhase": "Default", - "AdditionalDependencies": [ - "Engine" - ] - } - ], - "Plugins": [ - { - "Name": "ModelingToolsEditorMode", - "Enabled": true, - "TargetAllowList": [ - "Editor" - ] - }, - { - "Name": "FunctionalTestingEditor", - "Enabled": true - }, - { - "Name": "Nakama", - "Enabled": true - }, - { - "Name": "Satori", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/NakamaBlueprintsTest/README.md b/NakamaBlueprintsTest/README.md deleted file mode 100644 index c655181c9..000000000 --- a/NakamaBlueprintsTest/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Nakama Blueprints Tests - -**Nakama Blueprints Tests** is a dedicated project aimed at validating the blueprint logic using Unreal's Automated Testing systems. - -- 🎮 **Version Compatibility:** Primarily tested on Unreal Engine 5.2. -- 📁 **Dependencies:** The Nakama plugin is bundled within the `Plugins` directory. - -## 🚀 Getting Started - -Follow these steps to get the project up and running: - -1. **Nakama Server Setup:** - - Install and boot up the Nakama server. - - Detailed [instructions available here](https://heroiclabs.com/docs/install-docker-quickstart). - -2. **Unreal Engine Installation:** - - Make sure to have [Unreal Engine](https://www.unrealengine.com) version 5.0 or later. - -3. **Project Initialization:** - - Right-click the `.uproject` file and select `Generate Visual Studio Project Files`. - -4. **IDE Configuration:** - - Open the generated solution in your preferred Unreal-compatible IDE (e.g., Visual Studio, Xcode, Rider). - - Launch the project. - -5. **Server Credential Configuration:** - - If using non-default server credentials or a remote server, navigate to `Content/Tests/Base` and open `Nakama_Test_Base`. - - Adjust the server parameters in the `CreateClient` function. - -**Note: You might also be able to open the project directly after adding the Nakama Plugin into the project** - -## 🧪 Running the Tests - -To execute the Nakama tests: - -1. In the Unreal Editor, head to `Tools` -> `Test Automation`. -2. Select your testing device (e.g., YOURNAME_123). -3. Under `Project` -> `FunctionalTests`, you'll find all the `Nakama Tests`. -4. Select the desired tests (or select all) and press `Start Tests`. diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest.Target.cs b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest.Target.cs deleted file mode 100644 index e4c035b68..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest.Target.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaBlueprintsTestTarget : TargetRules -{ - public NakamaBlueprintsTestTarget(TargetInfo Target) : base(Target) - { - Type = TargetType.Game; - DefaultBuildSettings = BuildSettingsVersion.V5; - IncludeOrderVersion = EngineIncludeOrderVersion.Latest; - - //bIWYU = true; - bEnforceIWYU = true; - bUseUnityBuild = false; - bForceUnityBuild = false; - bUsePCHFiles = false; - - ExtraModuleNames.AddRange( new string[] { "NakamaBlueprintsTest" } ); - } -} diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.Build.cs b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.Build.cs deleted file mode 100644 index 526f2fe8a..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.Build.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -using UnrealBuildTool; - -public class NakamaBlueprintsTest : ModuleRules -{ - public NakamaBlueprintsTest(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json", "JsonUtilities" }); - - PrivateDependencyModuleNames.AddRange(new string[] { "NakamaUnreal" }); - } -} diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.cpp b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.cpp deleted file mode 100644 index f19bb9421..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "NakamaBlueprintsTest.h" -#include "Modules/ModuleManager.h" - -IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, NakamaBlueprintsTest, "NakamaBlueprintsTest" ); diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.h b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.h deleted file mode 100644 index 90aad9e7e..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/NakamaBlueprintsTest.h +++ /dev/null @@ -1,6 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" - diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Private/NakamaTestFunctionLibrary.cpp b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Private/NakamaTestFunctionLibrary.cpp deleted file mode 100644 index a95c19cb4..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Private/NakamaTestFunctionLibrary.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "NakamaTestFunctionLibrary.h" -#include "Serialization/JsonSerializer.h" -#include "Dom/JsonObject.h" - -FString UNakamaTestFunctionLibrary::GetMatchIdFromJsonString(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - FString MatchId; - if (JsonObject->TryGetStringField(TEXT("match_id"), MatchId)) - { - return MatchId; - } - } - - // Return an empty string if the parsing fails or if the "match_id" field is not present - return FString(); -} - -FString UNakamaTestFunctionLibrary::GenerateTournamentJSONString(bool bAuthoritative, const FString& SortOrder, - const FString& Operator, int32 Duration, const FString& ResetSchedule, const FString& Title, - const FString& Description, int32 Category, int64 StartTime, int64 EndTime, int32 MaxSize, int32 MaxNumScore, - bool bJoinRequired) -{ - TSharedPtr JsonObject = MakeShareable(new FJsonObject); - - JsonObject->SetBoolField("authoritative", bAuthoritative); - JsonObject->SetStringField("sort_order", SortOrder); - JsonObject->SetStringField("operator", Operator); - JsonObject->SetNumberField("duration", Duration); - JsonObject->SetStringField("reset_schedule", ResetSchedule); - JsonObject->SetStringField("title", Title); - JsonObject->SetStringField("description", Description); - JsonObject->SetNumberField("category", Category); - JsonObject->SetNumberField("start_time", StartTime); - JsonObject->SetNumberField("end_time", EndTime); - JsonObject->SetNumberField("max_size", MaxSize); - JsonObject->SetNumberField("max_num_score", MaxNumScore); - JsonObject->SetBoolField("join_required", bJoinRequired); - - FString JsonString; - TSharedRef> JsonWriter = TJsonWriterFactory::Create(&JsonString); - FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); - - return JsonString; -} - -int64 UNakamaTestFunctionLibrary::GetCurrentUnixTimestampInSeconds() -{ - return FDateTime::Now().ToUnixTimestamp(); -} - -FString UNakamaTestFunctionLibrary::GetTournamentIdFromJsonString(const FString& JsonString) -{ - TSharedPtr JsonObject; - TSharedRef> JsonReader = TJsonReaderFactory::Create(JsonString); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - FString TournamentId; - if (JsonObject->TryGetStringField("tournament_id", TournamentId)) - { - return TournamentId; - } - } - - // Return an empty string if the parsing fails or if the "tournament_id" field is not present - return FString(); -} diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Public/NakamaTestFunctionLibrary.h b/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Public/NakamaTestFunctionLibrary.h deleted file mode 100644 index b87d30479..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTest/Public/NakamaTestFunctionLibrary.h +++ /dev/null @@ -1,45 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "NakamaTestFunctionLibrary.generated.h" - -/** - * - */ -UCLASS() -class NAKAMABLUEPRINTSTEST_API UNakamaTestFunctionLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -public: - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Nakama|Test") - static FString GetMatchIdFromJsonString(const FString& JsonString); - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "JSON Generation", meta = (DisplayName = "Generate Tournament JSON String")) - static FString GenerateTournamentJSONString( - bool bAuthoritative, - const FString& SortOrder, - const FString& Operator, - int32 Duration, - const FString& ResetSchedule, - const FString& Title, - const FString& Description, - int32 Category, - int64 StartTime, - int64 EndTime, - int32 MaxSize, - int32 MaxNumScore, - bool bJoinRequired - ); - - UFUNCTION(BlueprintPure, Category = "Nakama|Test|Utilities", meta = (DisplayName = "Get Current Unix Timestamp In Seconds")) - static int64 GetCurrentUnixTimestampInSeconds(); - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Nakama|Test") - static FString GetTournamentIdFromJsonString(const FString& JsonString); - -}; diff --git a/NakamaBlueprintsTest/Source/NakamaBlueprintsTestEditor.Target.cs b/NakamaBlueprintsTest/Source/NakamaBlueprintsTestEditor.Target.cs deleted file mode 100644 index 52e507f70..000000000 --- a/NakamaBlueprintsTest/Source/NakamaBlueprintsTestEditor.Target.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaBlueprintsTestEditorTarget : TargetRules -{ - public NakamaBlueprintsTestEditorTarget(TargetInfo Target) : base(Target) - { - Type = TargetType.Editor; - DefaultBuildSettings = BuildSettingsVersion.V5; - IncludeOrderVersion = EngineIncludeOrderVersion.Latest; - - //bIWYU = true; - bEnforceIWYU = true; - bUseUnityBuild = false; - bForceUnityBuild = false; - bUsePCHFiles = false; - - ExtraModuleNames.AddRange( new string[] { "NakamaBlueprintsTest" } ); - } -} diff --git a/NakamaTest/Config/DefaultEditor.ini b/NakamaTest/Config/DefaultEditor.ini deleted file mode 100644 index e69de29bb..000000000 diff --git a/NakamaTest/Config/DefaultInput.ini b/NakamaTest/Config/DefaultInput.ini deleted file mode 100644 index 1ff89cc17..000000000 --- a/NakamaTest/Config/DefaultInput.ini +++ /dev/null @@ -1,86 +0,0 @@ -[/Script/Engine.InputSettings] --AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) --AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) --AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) --AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) -+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) -bAltEnterTogglesFullscreen=True -bF11TogglesFullscreen=True -bUseMouseForTouch=False -bEnableMouseSmoothing=True -bEnableFOVScaling=True -bCaptureMouseOnLaunch=True -bEnableLegacyInputScales=True -bEnableMotionControls=True -bFilterInputByPlatformUser=False -bEnableInputDeviceSubsystem=True -bShouldFlushPressedKeysOnViewportFocusLost=True -bEnableDynamicComponentInputBinding=True -bAlwaysShowTouchInterface=False -bShowConsoleOnFourFingerTap=True -bEnableGestureRecognizer=False -bUseAutocorrect=False -DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown -DefaultViewportMouseLockMode=LockOnCapture -FOVScale=0.011110 -DoubleClickTime=0.200000 -DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput -DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent -DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks --ConsoleKeys=Tilde -+ConsoleKeys=Tilde - diff --git a/NakamaTest/Content/Maps/EmptyMap.umap b/NakamaTest/Content/Maps/EmptyMap.umap deleted file mode 100644 index 121906a6c..000000000 Binary files a/NakamaTest/Content/Maps/EmptyMap.umap and /dev/null differ diff --git a/NakamaTest/README.md b/NakamaTest/README.md deleted file mode 100644 index f77b1b882..000000000 --- a/NakamaTest/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Nakama Test Project -This is a test Unreal Engine project testing the core features of the Nakama Unreal Engine plugin. - -# Build and Test -Build the Nakama plugin and place it in the Plugins folder. - -Follow these instructions to package the example project: - -https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Packaging/ - -Follow these instructions to package the test project: - -https://docs.unrealengine.com/4.27/en-US/Basics/Projects/Packaging/ - -### Windows - -To build the test, run: - -`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -targetconfig=Debug -project="$NAKAMA_UNREAL/NakamaTest/NakamaTest.uproject" -noP4 -installed -utf8output -build -cook -stage -package -verbose -stdout -nohostplatform -useshellexecute` - -To run the test, run: - -```bash -./NakamaTest/Saved/StagedBuilds/Windows/NakamaTest.exe -nullrhi -verbose -ExecCmds="Automation RunTests NakamaTest.Core" -``` - -There appears to be a problem with logging to stdout on Windows, so you'll need to obtain the test logs from `NakamaTest\Saved\StagedBuilds\Windows\NakamaTest\Saved\Logs\NakamaTest.log`. - -### Mac - -To build the test, run: - -`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project="$NAKAMA_UNREAL/NakamaTest/NakamaTest.uproject" -targetConfig=Debug -noP4 -platform=Mac -Architecture_Mac=arm64 -targetconfig=Debug -installed -unrealexe=UnrealEditor -utf8output -build -cook -stage -package -verbose` - -To run the test, run: - -`./NakamaTest/Binaries/Mac/NakamaTest.app/Contents/MacOS/NakamaTest -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests NakamaTest.Core"` - -You can pass `List` instead to the `Automation` command to view all tests. It will include engine tests. - -You can also pass `-LogCmds=" verbose"` where `` is one of those defined by `DEFINE_LOG_CATEGORY`. - -### Linux - -To build the test, run: - -`$UNREAL_ENGINE/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project="$NAKAMA_UNREAL/NakamaTest/NakamaTest.uproject" -clientconfig=Test -noP4 -platform=Linux -targetconfig=Debug -installed -utf8output -build -cook -stage -package -verbose` - -To run the test: - -`NakamaTest/Binaries/Linux/NakamaTest-Linux-Test -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests NakamaTest.Core"` - -To debug with LLDB on Mac: - -`lldb ${NAKAMA_UNREAL}/NakamaTest/Binaries/Mac/NakamaTest-Mac-Test.app/Contents/MacOS/NakamaTest-Mac-Test` - -Set the startup args inside the lldb shell: - -`settings set -- target.run-args -nullrhi -stdout -forcelogflush -ExecCmds="Automation RunTests NakamaTest.Core"` - -Then call `run`. diff --git a/NakamaTest/Source/NakamaTest.Target.cs b/NakamaTest/Source/NakamaTest.Target.cs deleted file mode 100644 index 52777b3b1..000000000 --- a/NakamaTest/Source/NakamaTest.Target.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaTestTarget : TargetRules -{ - public NakamaTestTarget( TargetInfo Target) : base(Target) - { - Type = TargetType.Game; - DefaultBuildSettings = BuildSettingsVersion.Latest; - ExtraModuleNames.AddRange( new string[] { "NakamaTest" } ); - bUseLoggingInShipping = true; - } -} diff --git a/NakamaTest/Source/NakamaTest/FNakamaTestCore.cpp b/NakamaTest/Source/NakamaTest/FNakamaTestCore.cpp deleted file mode 100644 index c2ba3cdf1..000000000 --- a/NakamaTest/Source/NakamaTest/FNakamaTestCore.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023 The Nakama Authors. - -#include "CoreMinimal.h" -#include "nakama-test/NTestLib.h" -#include "NakamaCoreClientFactory.h" -#include "WebSocketsModule.h" - -#define SERVER_KEY "defaultkey" -#define SERVER_HTTP_KEY "defaulthttpkey" -#define SERVER_HOST "127.0.0.1" -#define SERVER_HTTP_PORT 7350 -#define SERVER_SSL false -#define SERVER_PORT SERVER_HTTP_PORT - -DEFINE_LOG_CATEGORY_STATIC(NakamaTestLog, Log, All) - -IMPLEMENT_SIMPLE_AUTOMATION_TEST(FNakamaTestCore, "NakamaTest.Core", EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter) - -bool FNakamaTestCore::RunTest(const FString &Parameters) -{ - UE_LOG(LogTemp, Log, TEXT("%hs"), "Running tests"); - - // enqueue the test to run in a separate thread so it doesn't block the game thread, where requests come in on. - FAutomationTestFramework::Get().EnqueueLatentCommand(MakeShareable(new FThreadedAutomationLatentCommand([]() - { - - Nakama::Test::runAllTests( - [](Nakama::NClientParameters parameters) -> Nakama::NClientPtr - { - return NakamaCoreClientFactory::createNakamaClient(parameters, Nakama::NLogLevel::Debug); - }, - [](Nakama::NClientPtr client) -> Nakama::NRtClientPtr - { - return NakamaCoreClientFactory::createNakamaRtClient(client); - }, - {SERVER_KEY, SERVER_HOST, SERVER_PORT, SERVER_SSL}, SERVER_HTTP_KEY); - }))); - - return true; -} \ No newline at end of file diff --git a/NakamaTest/Source/NakamaTest/NakamaTest.Build.cs b/NakamaTest/Source/NakamaTest/NakamaTest.Build.cs deleted file mode 100644 index c632967db..000000000 --- a/NakamaTest/Source/NakamaTest/NakamaTest.Build.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2023 The Nakama Authors. - -using System; -using System.IO; -using UnrealBuildTool; - -public class NakamaTest : ModuleRules -{ - public NakamaTest(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PrivateDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NakamaCore", "WebSockets" }); - PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "libnakamatest")); - - if (Target.Platform == UnrealTargetPlatform.Mac) - { - string dylibPath; - if (Target.Architecture == UnrealArch.Arm64) - { - dylibPath = Path.Combine(ModuleDirectory, "libnakamatest", "libnakama-test.dylib"); - } - else - { - throw new InvalidOperationException("Unrecognized OSX architecture"); - } - - PublicDelayLoadDLLs.Add(dylibPath); - RuntimeDependencies.Add(dylibPath); - } - else if (Target.Platform == UnrealTargetPlatform.Win64) - { - string configurationDirectory = null; - string arch = null; - - if (Target.Configuration == UnrealTargetConfiguration.Debug) - { - configurationDirectory = "Debug"; - } - else - { - configurationDirectory = "Release"; - } - - if (Target.Architecture == UnrealArch.X64) - { - arch = "win-x64"; - } - else if (Target.Architecture == UnrealArch.Arm64) - { - arch = "win-arm64"; - } - else - { - throw new InvalidOperationException("Unrecognized Windows architecture"); - } - - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakamatest", arch, configurationDirectory, "nakama-test.lib")); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", "nakama-test.dll"), Path.Combine(ModuleDirectory, "libnakamatest", arch, configurationDirectory, "nakama-test.dll")); - } - else if (Target.Platform == UnrealTargetPlatform.Linux) - { - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libnakamatest", "linux", "libnakama-test.so")); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", Path.GetFileName("libnakama-test.so")), Path.Combine(ModuleDirectory, "libnakamatest", "linux", "libnakama-test.so")); - } - else - { - throw new BuildException("Unsupported platform"); - } - } -} diff --git a/NakamaTest/Source/NakamaTest/NakamaTest.cpp b/NakamaTest/Source/NakamaTest/NakamaTest.cpp deleted file mode 100644 index bacdcdea5..000000000 --- a/NakamaTest/Source/NakamaTest/NakamaTest.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2023 The Nakama Authors. - -#include "NakamaTest.h" -#include "CoreMinimal.h" - -IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, NakamaTest, "NakamaTest"); diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/libnakama-test.dylib b/NakamaTest/Source/NakamaTest/libnakamatest/libnakama-test.dylib deleted file mode 100755 index 8c46f33e6..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/libnakama-test.dylib and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/linux/libnakama-test.so b/NakamaTest/Source/NakamaTest/libnakamatest/linux/libnakama-test.so deleted file mode 100644 index 134690bcc..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/linux/libnakama-test.so and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/nakama-test/NTestLib.h b/NakamaTest/Source/NakamaTest/libnakamatest/nakama-test/NTestLib.h deleted file mode 100644 index a97bb3d48..000000000 --- a/NakamaTest/Source/NakamaTest/libnakamatest/nakama-test/NTestLib.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "nakama-cpp/NUtils.h" -#include "nakama-cpp/NPlatformParams.h" -#include "nakama-cpp/ClientFactory.h" - -#if defined NAKAMA_TEST_SHARED_LIBRARY && (defined _WIN32 || defined __CYGWIN__ || defined FORCE_DLL_IMPORT_EXPORT) - #ifdef NAKAMA_TEST_SHARED_LIBRARY_EXPORTS - #define NAKAMA_TEST_API __declspec(dllexport) - #else - #define NAKAMA_TEST_API __declspec(dllimport) - #endif -#elif defined NAKAMA_TEST_SHARED_LIBRARY && __GNUC__ >= 4 - #ifdef NAKAMA_TEST_SHARED_LIBRARY_EXPORTS - #define NAKAMA_TEST_API __attribute__((visibility("default"))) - #else - #define NAKAMA_TEST_API - #endif -#else - #define NAKAMA_TEST_API -#endif - - - -namespace Nakama { -namespace Test { - -NAKAMA_TEST_API int runAllTests(std::function clientFactory, std::function rtClientFactory, NClientParameters parameters, std::string serverHttpKey); - -} -} \ No newline at end of file diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nro b/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nro deleted file mode 100644 index fce225212..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nro and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nrs b/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nrs deleted file mode 100644 index fd5d6c087..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/switch/nakama-test.nrs and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.dll b/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.dll deleted file mode 100644 index 97ffe01a7..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.dll and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.lib b/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.lib deleted file mode 100644 index 161eb46da..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Debug/nakama-test.lib and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.dll b/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.dll deleted file mode 100644 index d697b1ab7..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.dll and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.lib b/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.lib deleted file mode 100644 index 161eb46da..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-arm64/Release/nakama-test.lib and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.dll b/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.dll deleted file mode 100644 index f89b78cab..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.dll and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.lib b/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.lib deleted file mode 100644 index 0fff40bf6..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Debug/nakama-test.lib and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.dll b/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.dll deleted file mode 100644 index 792501d8a..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.dll and /dev/null differ diff --git a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.lib b/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.lib deleted file mode 100644 index 0fff40bf6..000000000 Binary files a/NakamaTest/Source/NakamaTest/libnakamatest/win-x64/Release/nakama-test.lib and /dev/null differ diff --git a/NakamaTest/Source/NakamaTestEditor.Target.cs b/NakamaTest/Source/NakamaTestEditor.Target.cs deleted file mode 100644 index ec4cbf04e..000000000 --- a/NakamaTest/Source/NakamaTestEditor.Target.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 The Nakama Authors. - -using UnrealBuildTool; -using System.Collections.Generic; - -public class NakamaTestEditorTarget : TargetRules -{ - public NakamaTestEditorTarget( TargetInfo Target) : base(Target) - { - Type = TargetType.Editor; - DefaultBuildSettings = BuildSettingsVersion.Latest; - ExtraModuleNames.AddRange( new string[] { "NakamaTest" } ); - } -} \ No newline at end of file diff --git a/README.md b/README.md index 87b003e39..204495c56 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ This plugin can also be used for programmers who like C++ or Blueprints. All var The plugin is divided into three modules which can be pulled in depending on your needs. -- `NakamaUnreal` The recommended C++-based module for using Nakama in UnrealEngine. This integrates with Unreal's native types and UObjects. +- `Nakama` The recommended C++-based module for using Nakama in UnrealEngine. This integrates with Unreal's native types and UObjects. - `NakamaBlueprints` For users who would prefer to use Blueprints in their project. - `NakamaCore` For users who are migrating from pre-v2.6.0 Unreal client and want to make minimal changes, or use our lower-level C++ API without Unreal types: (https://github.com/heroiclabs/nakama-cpp). -> Because `NakamaCore` uses prebuilt libraries, you may run into build issues due to differences in Unreal's toolchain from standard C++ toolchains. This is why `NakamaUnreal` is our recommended module. +> Because `NakamaCore` uses prebuilt libraries, you may run into build issues due to differences in Unreal's toolchain from standard C++ toolchains. This is why `Nakama` is our recommended module. Clients are referred in this documentation as **Realtime Client** and **Default Client** in which the realtime client is the socket and the default client is using REST API to communicate with Nakama. @@ -60,15 +60,15 @@ You have to decide where you want to create and keep record of these clients. Normally you would have to handle ticking on a C++ basis, luckily this is done automatically under the hood in this plugin after you have created the client. When you create the Client you can define a Tick Interval, by default this is set to 0, which means it will tick every frame, if you want it to tick every 50ms you have to set it as 0.05, to make it tick every second this number would be 1. -# Getting Started with NakamaUnreal (C++ with Unreal Types) +# Getting Started with Nakama (C++ with Unreal Types) Below is a simple example of setting up a default client, authenticating, setting up a realtime client and joining a chat room. In the example we will put everything in a empty AActor class that is placed in the level. -Remember to add NakamaUnreal to your Private Dependencies under your project Build.cs file. For example: +Remember to add Nakama to your Private Dependencies under your project Build.cs file. For example: ```cs -PrivateDependencyModuleNames.AddRange(new string[] { "NakamaUnreal" }); +PrivateDependencyModuleNames.AddRange(new string[] { "Nakama" }); ``` diff --git a/Satori/Satori.uplugin b/Satori/Satori.uplugin index 20f4798fe..88cbbe4b7 100644 --- a/Satori/Satori.uplugin +++ b/Satori/Satori.uplugin @@ -16,7 +16,19 @@ "Installed": false, "Modules": [ { - "Name": "SatoriUnreal", + "Name": "SatoriApi", + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ + "Win64", + "Linux", + "IOS", + "Mac", + "Android" + ] + }, + { + "Name": "Satori", "Type": "Runtime", "LoadingPhase": "Default", "WhitelistPlatforms": [ diff --git a/Satori/Source/Satori/Private/Satori.cpp b/Satori/Source/Satori/Private/Satori.cpp new file mode 100644 index 000000000..c6893cb65 --- /dev/null +++ b/Satori/Source/Satori/Private/Satori.cpp @@ -0,0 +1,1286 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "Satori.h" +#include "Containers/Ticker.h" +#include "Modules/ModuleManager.h" + +bool Satori::IsTransientError(const FSatoriError& Error) noexcept +{ + switch (Error.Code) + { + case 0: // Connection failed (TCP refused, DNS timeout, etc.) + case 4: // DEADLINE_EXCEEDED + case 13: // INTERNAL + case 14: // UNAVAILABLE + return true; + default: + return false; + } +} + +float Satori::CalculateBackoff(int32 Attempt, const FSatoriRetryConfig& Config) noexcept +{ + const float ExponentialDelay = FMath::Pow(2.0f, static_cast(Attempt - 1)) * Config.BaseDelayMs; + const float Jitter = FMath::FRand() * ExponentialDelay; + return Jitter / 1000.0f; +} + +namespace +{ + +/** Optionally refresh the session before calling the RPC. */ +void MaybeRefreshThenCall( + const TSharedRef& SessionState, + const FSatoriClientConfig& ClientConfig, + const FSatoriRetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)(FSatoriError(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + SatoriApi::AuthenticateRefresh( + ClientConfig, + SessionState->RefreshToken, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const FSatoriSession& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const FSatoriError& Error) + { + (*OnError)(FSatoriError(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace +TSatoriFuture Satori::Authenticate( + const FSatoriClientConfig& ClientConfig, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , NoSession + , Default + , Custom + ]() + { + SatoriApi::Authenticate( + ClientConfig, + Id, + NoSession, + Default, + Custom, + [FutureState, DoRequest, OnError](const FSatoriSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::AuthenticateLogout( + const FSatoriClientConfig& ClientConfig, + FString Token, + FString RefreshToken, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Token + , RefreshToken + ]() + { + SatoriApi::AuthenticateLogout( + ClientConfig, + Token, + RefreshToken, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::AuthenticateRefresh( + const FSatoriClientConfig& ClientConfig, + FString RefreshToken, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , RefreshToken + ]() + { + SatoriApi::AuthenticateRefresh( + ClientConfig, + RefreshToken, + [FutureState, DoRequest, OnError](const FSatoriSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::DeleteIdentity( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + SatoriApi::DeleteIdentity( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::Event( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Events, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Events + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Events + ]() + { + SatoriApi::Event( + ClientConfig, + *SessionState, + Events, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::ServerEvent( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Events, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Events + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Events + ]() + { + SatoriApi::ServerEvent( + ClientConfig, + *SessionState, + Events, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::GetExperiments( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriExperimentListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + SatoriApi::GetExperiments( + ClientConfig, + *SessionState, + Names, + Labels, + [FutureState, DoRequest, OnError](const FSatoriExperimentList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriExperimentListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::GetFlagOverrides( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriFlagOverrideListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + SatoriApi::GetFlagOverrides( + ClientConfig, + *SessionState, + Names, + Labels, + [FutureState, DoRequest, OnError](const FSatoriFlagOverrideList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriFlagOverrideListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::GetFlags( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriFlagListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + ]() + { + SatoriApi::GetFlags( + ClientConfig, + *SessionState, + Names, + Labels, + [FutureState, DoRequest, OnError](const FSatoriFlagList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriFlagListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::GetLiveEvents( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriLiveEventListResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + , PastRunCount + , FutureRunCount + , StartTimeSec + , EndTimeSec + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Names + , Labels + , PastRunCount + , FutureRunCount + , StartTimeSec + , EndTimeSec + ]() + { + SatoriApi::GetLiveEvents( + ClientConfig, + *SessionState, + Names, + Labels, + PastRunCount, + FutureRunCount, + StartTimeSec, + EndTimeSec, + [FutureState, DoRequest, OnError](const FSatoriLiveEventList& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriLiveEventListResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::JoinLiveEvent( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + ]() + { + SatoriApi::JoinLiveEvent( + ClientConfig, + *SessionState, + Id, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::Healthcheck( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + SatoriApi::Healthcheck( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::Identify( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Default + , Custom + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , Default + , Custom + ]() + { + SatoriApi::Identify( + ClientConfig, + *SessionState, + Id, + Default, + Custom, + [FutureState, DoRequest, OnError](const FSatoriSession& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriSessionResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::ListProperties( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriPropertiesResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + SatoriApi::ListProperties( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError](const FSatoriProperties& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriPropertiesResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::Readycheck( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + ]() + { + SatoriApi::Readycheck( + ClientConfig, + *SessionState, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::UpdateProperties( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Recompute + , Default + , Custom + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Recompute + , Default + , Custom + ]() + { + SatoriApi::UpdateProperties( + ClientConfig, + *SessionState, + Recompute, + Default, + Custom, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::GetMessageList( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriGetMessageListResponseResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Forward + , Cursor + , MessageIds + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Limit + , Forward + , Cursor + , MessageIds + ]() + { + SatoriApi::GetMessageList( + ClientConfig, + *SessionState, + Limit, + Forward, + Cursor, + MessageIds, + [FutureState, DoRequest, OnError](const FSatoriGetMessageListResponse& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriGetMessageListResponseResult{Result, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::UpdateMessage( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , ReadTime + , ConsumeTime + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + , ReadTime + , ConsumeTime + ]() + { + SatoriApi::UpdateMessage( + ClientConfig, + *SessionState, + Id, + ReadTime, + ConsumeTime, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} +TSatoriFuture Satori::DeleteMessage( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto SessionState = MakeShared(Session); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{{}, Error, true}); + } + }; + + *DoRequest = [FutureState,SessionState,DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + ]() + { + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + , Id + ]() + { + SatoriApi::DeleteMessage( + ClientConfig, + *SessionState, + Id, + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); + }; + + (*DoRequest)(); + + return TSatoriFuture(FutureState); +} + +// Module implementation +class FSatoriModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogSatori, Log, TEXT("Satori module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogSatori, Log, TEXT("Satori module shutting down")); + } +}; + +IMPLEMENT_MODULE(FSatoriModule, Satori) diff --git a/Satori/Source/Satori/Public/Satori.h b/Satori/Source/Satori/Public/Satori.h new file mode 100644 index 000000000..b9b74ba47 --- /dev/null +++ b/Satori/Source/Satori/Public/Satori.h @@ -0,0 +1,314 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "AsyncFuture.h" +#include "SatoriApi.h" + +/** Tag type used as the value type for RPCs that return no data. */ +struct SATORI_API FSatoriVoid {}; + +struct SATORI_API FSatoriVoidResult +{ + using ValueType = FSatoriVoid; + FSatoriVoid Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriSessionResult +{ + using ValueType = FSatoriSession; + FSatoriSession Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriExperimentListResult +{ + using ValueType = FSatoriExperimentList; + FSatoriExperimentList Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriFlagOverrideListResult +{ + using ValueType = FSatoriFlagOverrideList; + FSatoriFlagOverrideList Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriFlagListResult +{ + using ValueType = FSatoriFlagList; + FSatoriFlagList Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriLiveEventListResult +{ + using ValueType = FSatoriLiveEventList; + FSatoriLiveEventList Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriPropertiesResult +{ + using ValueType = FSatoriProperties; + FSatoriProperties Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +struct SATORI_API FSatoriGetMessageListResponseResult +{ + using ValueType = FSatoriGetMessageListResponse; + FSatoriGetMessageListResponse Value{}; + FSatoriError Error; + bool bIsError = true; +}; + +/** + * Satori-flavoured alias for TAsyncFuture. + * All three .Next() overloads are inherited from TAsyncFuture and dispatch + * user callbacks to the game thread, making it safe to touch UObject*, + * fire delegates, or update UI from any .Next() callback. + */ +template +using TSatoriFuture = TAsyncFuture; + +/** Type trait for TSatoriFuture (delegates to TIsTAsyncFuture). */ +template using TIsTSatoriFuture = TIsTAsyncFuture; + +/** Retry configuration for transient error handling + session auto-refresh. */ +struct SATORI_API FSatoriRetryConfig +{ + /** Maximum number of retry attempts (0 = no retries). */ + int32 MaxRetries = 4; + + /** Base delay in milliseconds before first retry. Actual delay uses exponential backoff with full jitter. */ + int32 BaseDelayMs = 500; + + /** Whether to automatically refresh expired sessions before making API calls. */ + bool bAutoRefreshSession = true; + + /** How many seconds before expiry a session is considered "about to expire" and gets refreshed. */ + int64 AutoRefreshBufferSeconds = 300; + + /** HTTP request timeout in seconds. */ + float Timeout = 10.0f; + + /** + * Called after a session is automatically refreshed, so callers can persist the updated session. + * + * If the callback captures a UObject pointer, set OnSessionRefreshedOwner to that object. + * The callback will be skipped if the owner has been garbage-collected, preventing + * dangling-pointer crashes during in-flight retries. + */ + TFunction OnSessionRefreshed; + + /** Optional weak owner for OnSessionRefreshed. When set, the callback is skipped if the owner is stale. */ + TWeakObjectPtr OnSessionRefreshedOwner; +}; + +/** + * High-level Satori API: free functions with retry logic + session auto-refresh. + * + * Transient errors (UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED) are + * automatically retried with exponential backoff and full jitter. + */ +namespace Satori +{ + /** Returns true if the error code is considered transient (eligible for retry). */ + SATORI_API bool IsTransientError(const FSatoriError& Error) noexcept; + + /** Compute backoff delay in seconds for a given attempt using exponential backoff with full jitter. */ + SATORI_API float CalculateBackoff(int32 Attempt, const FSatoriRetryConfig& Config) noexcept; + + /** Authenticate against the server. */ + SATORI_API TSatoriFuture Authenticate( + const FSatoriClientConfig& ClientConfig, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ + SATORI_API TSatoriFuture AuthenticateLogout( + const FSatoriClientConfig& ClientConfig, + FString Token, + FString RefreshToken, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */ + SATORI_API TSatoriFuture AuthenticateRefresh( + const FSatoriClientConfig& ClientConfig, + FString RefreshToken, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the caller's identity and associated data. */ + SATORI_API TSatoriFuture DeleteIdentity( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Publish an event for this session. */ + SATORI_API TSatoriFuture Event( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Events, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Publish server events for multiple distinct identities. */ + SATORI_API TSatoriFuture ServerEvent( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Events, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get or list all available experiments for this identity. */ + SATORI_API TSatoriFuture GetExperiments( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all available flags and their value overrides for this identity. */ + SATORI_API TSatoriFuture GetFlagOverrides( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all available flags for this identity. */ + SATORI_API TSatoriFuture GetFlags( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List available live events. */ + SATORI_API TSatoriFuture GetLiveEvents( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Join an 'explicit join' live event. */ + SATORI_API TSatoriFuture JoinLiveEvent( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. */ + SATORI_API TSatoriFuture Healthcheck( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Enrich/replace the current session with new identifier. */ + SATORI_API TSatoriFuture Identify( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List properties associated with this identity. */ + SATORI_API TSatoriFuture ListProperties( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A readycheck which load balancers can use to check the service. */ + SATORI_API TSatoriFuture Readycheck( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update identity properties. */ + SATORI_API TSatoriFuture UpdateProperties( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get the list of messages for the identity. */ + SATORI_API TSatoriFuture GetMessageList( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Updates a message for an identity. */ + SATORI_API TSatoriFuture UpdateMessage( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Deletes a message for an identity. */ + SATORI_API TSatoriFuture DeleteMessage( + const FSatoriClientConfig& ClientConfig, + const FSatoriSession& Session, + FString Id, + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +} diff --git a/Satori/Source/Satori/Satori.Build.cs b/Satori/Source/Satori/Satori.Build.cs new file mode 100644 index 000000000..6adac7c41 --- /dev/null +++ b/Satori/Source/Satori/Satori.Build.cs @@ -0,0 +1,77 @@ +/* +* Copyright 2025 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using UnrealBuildTool; +using System.IO; + +public class Satori : ModuleRules +{ + public Satori(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", "SatoriApi", "HTTP", "JsonUtilities" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "Engine", + "JsonUtilities", + "Json", + "HTTP" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + } +} diff --git a/Satori/Source/SatoriApi/Private/SatoriApi.cpp b/Satori/Source/SatoriApi/Private/SatoriApi.cpp new file mode 100644 index 000000000..21e486e2d --- /dev/null +++ b/Satori/Source/SatoriApi/Private/SatoriApi.cpp @@ -0,0 +1,2444 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "SatoriApi.h" +#include "SatoriHttpHelper.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +#include "Modules/ModuleManager.h" + +DEFINE_LOG_CATEGORY(LogSatori); + +using namespace SatoriHttpInternal; + +// --- FSatoriSession JWT helpers --- + +bool FSatoriSession::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + // Base64url -> standard Base64 + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + // Pad to multiple of 4 + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void FSatoriSession::ParseTokens() noexcept +{ + IdentityId.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + + // Parse auth token + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("iid"))) + { + IdentityId = TokenPayload->GetStringField(TEXT("iid")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + } + + // Parse refresh token + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool FSatoriSession::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool FSatoriSession::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void FSatoriSession::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} + +FSatoriAuthenticateLogoutRequest FSatoriAuthenticateLogoutRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriAuthenticateLogoutRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("refresh_token"))) + { + Result.RefreshToken = Json->GetStringField(TEXT("refresh_token")); + } + return Result; +} + +TSharedPtr FSatoriAuthenticateLogoutRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Json->SetStringField(TEXT("refresh_token"), RefreshToken); + } + return Json; +} + +FSatoriAuthenticateRefreshRequest FSatoriAuthenticateRefreshRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriAuthenticateRefreshRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("refresh_token"))) + { + Result.RefreshToken = Json->GetStringField(TEXT("refresh_token")); + } + return Result; +} + +TSharedPtr FSatoriAuthenticateRefreshRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!RefreshToken.IsEmpty()) + { + Json->SetStringField(TEXT("refresh_token"), RefreshToken); + } + return Json; +} + +FSatoriAuthenticateRequest FSatoriAuthenticateRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriAuthenticateRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("no_session"))) + { + Result.NoSession = Json->GetBoolField(TEXT("no_session")); + } + if (Json->HasField(TEXT("default"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("default"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Default.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + if (Json->HasField(TEXT("custom"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("custom"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Custom.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriAuthenticateRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + Json->SetBoolField(TEXT("no_session"), NoSession); + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("custom"), MapObj); + } + return Json; +} + +FSatoriDeleteMessageRequest FSatoriDeleteMessageRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriDeleteMessageRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + return Result; +} + +TSharedPtr FSatoriDeleteMessageRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + return Json; +} + +FSatoriEvent FSatoriEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("timestamp"))) + { + Result.Timestamp = Json->GetStringField(TEXT("timestamp")); + } + if (Json->HasField(TEXT("identity_id"))) + { + Result.IdentityId = Json->GetStringField(TEXT("identity_id")); + } + if (Json->HasField(TEXT("session_id"))) + { + Result.SessionId = Json->GetStringField(TEXT("session_id")); + } + if (Json->HasField(TEXT("session_issued_at"))) + { + Result.SessionIssuedAt = static_cast(Json->GetNumberField(TEXT("session_issued_at"))); + } + if (Json->HasField(TEXT("session_expires_at"))) + { + Result.SessionExpiresAt = static_cast(Json->GetNumberField(TEXT("session_expires_at"))); + } + if (Json->HasField(TEXT("metadata"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("metadata"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Metadata.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + if (!Timestamp.IsEmpty()) + { + Json->SetStringField(TEXT("timestamp"), Timestamp); + } + if (!IdentityId.IsEmpty()) + { + Json->SetStringField(TEXT("identity_id"), IdentityId); + } + if (!SessionId.IsEmpty()) + { + Json->SetStringField(TEXT("session_id"), SessionId); + } + Json->SetNumberField(TEXT("session_issued_at"), SessionIssuedAt); + Json->SetNumberField(TEXT("session_expires_at"), SessionExpiresAt); + if (Metadata.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Metadata) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("metadata"), MapObj); + } + return Json; +} + +FSatoriEventRequest FSatoriEventRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriEventRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("events"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("events"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Events.Add(FSatoriEvent::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FSatoriEventRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Events.Num() > 0) + { + TArray> Array; + for (const auto& Item : Events) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("events"), Array); + } + return Json; +} + +FSatoriExperiment FSatoriExperiment::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriExperiment Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("phase_name"))) + { + Result.PhaseName = Json->GetStringField(TEXT("phase_name")); + } + if (Json->HasField(TEXT("phase_variant_name"))) + { + Result.PhaseVariantName = Json->GetStringField(TEXT("phase_variant_name")); + } + return Result; +} + +TSharedPtr FSatoriExperiment::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + if (!PhaseName.IsEmpty()) + { + Json->SetStringField(TEXT("phase_name"), PhaseName); + } + if (!PhaseVariantName.IsEmpty()) + { + Json->SetStringField(TEXT("phase_variant_name"), PhaseVariantName); + } + return Json; +} + +FSatoriExperimentList FSatoriExperimentList::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriExperimentList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("experiments"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("experiments"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Experiments.Add(FSatoriExperiment::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FSatoriExperimentList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Experiments.Num() > 0) + { + TArray> Array; + for (const auto& Item : Experiments) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("experiments"), Array); + } + return Json; +} + +FSatoriValueChangeReason FSatoriValueChangeReason::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriValueChangeReason Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("type"))) + { + Result.Type = static_cast(Json->GetIntegerField(TEXT("type"))); + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("variant_name"))) + { + Result.VariantName = Json->GetStringField(TEXT("variant_name")); + } + return Result; +} + +TSharedPtr FSatoriValueChangeReason::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("type"), static_cast(Type)); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!VariantName.IsEmpty()) + { + Json->SetStringField(TEXT("variant_name"), VariantName); + } + return Json; +} + +FSatoriFlag FSatoriFlag::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriFlag Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("condition_changed"))) + { + Result.ConditionChanged = Json->GetBoolField(TEXT("condition_changed")); + } + if (Json->HasField(TEXT("change_reason"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("change_reason"), NestedObj)) + { + Result.ChangeReason = FSatoriValueChangeReason::FromJson(*NestedObj); + } + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriFlag::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + Json->SetBoolField(TEXT("condition_changed"), ConditionChanged); + Json->SetObjectField(TEXT("change_reason"), ChangeReason.ToJson()); + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + return Json; +} + +FSatoriFlagList FSatoriFlagList::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriFlagList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("flags"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("flags"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Flags.Add(FSatoriFlag::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FSatoriFlagList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Flags.Num() > 0) + { + TArray> Array; + for (const auto& Item : Flags) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("flags"), Array); + } + return Json; +} + +FSatoriFlagOverrideValue FSatoriFlagOverrideValue::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriFlagOverrideValue Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("type"))) + { + Result.Type = static_cast(Json->GetIntegerField(TEXT("type"))); + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("variant_name"))) + { + Result.VariantName = Json->GetStringField(TEXT("variant_name")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("create_time_sec"))) + { + Result.CreateTimeSec = static_cast(Json->GetNumberField(TEXT("create_time_sec"))); + } + return Result; +} + +TSharedPtr FSatoriFlagOverrideValue::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("type"), static_cast(Type)); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!VariantName.IsEmpty()) + { + Json->SetStringField(TEXT("variant_name"), VariantName); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + Json->SetNumberField(TEXT("create_time_sec"), CreateTimeSec); + return Json; +} + +FSatoriFlagOverride FSatoriFlagOverride::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriFlagOverride Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("flag_name"))) + { + Result.FlagName = Json->GetStringField(TEXT("flag_name")); + } + if (Json->HasField(TEXT("overrides"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("overrides"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Overrides.Add(FSatoriFlagOverrideValue::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriFlagOverride::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!FlagName.IsEmpty()) + { + Json->SetStringField(TEXT("flag_name"), FlagName); + } + if (Overrides.Num() > 0) + { + TArray> Array; + for (const auto& Item : Overrides) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("overrides"), Array); + } + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + return Json; +} + +FSatoriFlagOverrideList FSatoriFlagOverrideList::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriFlagOverrideList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("flags"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("flags"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Flags.Add(FSatoriFlagOverride::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FSatoriFlagOverrideList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Flags.Num() > 0) + { + TArray> Array; + for (const auto& Item : Flags) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("flags"), Array); + } + return Json; +} + +FSatoriGetExperimentsRequest FSatoriGetExperimentsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriGetExperimentsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("names"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("names"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Names.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriGetExperimentsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Names.Num() > 0) + { + TArray> Array; + for (const auto& Item : Names) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("names"), Array); + } + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + return Json; +} + +FSatoriGetFlagsRequest FSatoriGetFlagsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriGetFlagsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("names"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("names"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Names.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriGetFlagsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Names.Num() > 0) + { + TArray> Array; + for (const auto& Item : Names) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("names"), Array); + } + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + return Json; +} + +FSatoriGetLiveEventsRequest FSatoriGetLiveEventsRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriGetLiveEventsRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("names"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("names"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Names.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + if (Json->HasField(TEXT("past_run_count"))) + { + Result.PastRunCount = Json->GetIntegerField(TEXT("past_run_count")); + } + if (Json->HasField(TEXT("future_run_count"))) + { + Result.FutureRunCount = Json->GetIntegerField(TEXT("future_run_count")); + } + if (Json->HasField(TEXT("start_time_sec"))) + { + Result.StartTimeSec = static_cast(Json->GetNumberField(TEXT("start_time_sec"))); + } + if (Json->HasField(TEXT("end_time_sec"))) + { + Result.EndTimeSec = static_cast(Json->GetNumberField(TEXT("end_time_sec"))); + } + return Result; +} + +TSharedPtr FSatoriGetLiveEventsRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Names.Num() > 0) + { + TArray> Array; + for (const auto& Item : Names) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("names"), Array); + } + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + Json->SetNumberField(TEXT("past_run_count"), PastRunCount); + Json->SetNumberField(TEXT("future_run_count"), FutureRunCount); + Json->SetNumberField(TEXT("start_time_sec"), StartTimeSec); + Json->SetNumberField(TEXT("end_time_sec"), EndTimeSec); + return Json; +} + +FSatoriMessage FSatoriMessage::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriMessage Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("schedule_id"))) + { + Result.ScheduleId = Json->GetStringField(TEXT("schedule_id")); + } + if (Json->HasField(TEXT("send_time"))) + { + Result.SendTime = static_cast(Json->GetNumberField(TEXT("send_time"))); + } + if (Json->HasField(TEXT("create_time"))) + { + Result.CreateTime = static_cast(Json->GetNumberField(TEXT("create_time"))); + } + if (Json->HasField(TEXT("update_time"))) + { + Result.UpdateTime = static_cast(Json->GetNumberField(TEXT("update_time"))); + } + if (Json->HasField(TEXT("read_time"))) + { + Result.ReadTime = static_cast(Json->GetNumberField(TEXT("read_time"))); + } + if (Json->HasField(TEXT("consume_time"))) + { + Result.ConsumeTime = static_cast(Json->GetNumberField(TEXT("consume_time"))); + } + if (Json->HasField(TEXT("text"))) + { + Result.Text = Json->GetStringField(TEXT("text")); + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("title"))) + { + Result.Title = Json->GetStringField(TEXT("title")); + } + if (Json->HasField(TEXT("image_url"))) + { + Result.ImageUrl = Json->GetStringField(TEXT("image_url")); + } + if (Json->HasField(TEXT("metadata"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("metadata"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Metadata.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriMessage::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!ScheduleId.IsEmpty()) + { + Json->SetStringField(TEXT("schedule_id"), ScheduleId); + } + Json->SetNumberField(TEXT("send_time"), SendTime); + Json->SetNumberField(TEXT("create_time"), CreateTime); + Json->SetNumberField(TEXT("update_time"), UpdateTime); + Json->SetNumberField(TEXT("read_time"), ReadTime); + Json->SetNumberField(TEXT("consume_time"), ConsumeTime); + if (!Text.IsEmpty()) + { + Json->SetStringField(TEXT("text"), Text); + } + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (!Title.IsEmpty()) + { + Json->SetStringField(TEXT("title"), Title); + } + if (!ImageUrl.IsEmpty()) + { + Json->SetStringField(TEXT("image_url"), ImageUrl); + } + if (Metadata.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Metadata) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("metadata"), MapObj); + } + return Json; +} + +FSatoriGetMessageListRequest FSatoriGetMessageListRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriGetMessageListRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("limit"))) + { + Result.Limit = Json->GetIntegerField(TEXT("limit")); + } + if (Json->HasField(TEXT("forward"))) + { + Result.Forward = Json->GetBoolField(TEXT("forward")); + } + if (Json->HasField(TEXT("cursor"))) + { + Result.Cursor = Json->GetStringField(TEXT("cursor")); + } + if (Json->HasField(TEXT("message_ids"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("message_ids"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.MessageIds.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriGetMessageListRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetNumberField(TEXT("limit"), Limit); + Json->SetBoolField(TEXT("forward"), Forward); + if (!Cursor.IsEmpty()) + { + Json->SetStringField(TEXT("cursor"), Cursor); + } + if (MessageIds.Num() > 0) + { + TArray> Array; + for (const auto& Item : MessageIds) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("message_ids"), Array); + } + return Json; +} + +FSatoriGetMessageListResponse FSatoriGetMessageListResponse::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriGetMessageListResponse Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("messages"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("messages"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.Messages.Add(FSatoriMessage::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("next_cursor"))) + { + Result.NextCursor = Json->GetStringField(TEXT("next_cursor")); + } + if (Json->HasField(TEXT("prev_cursor"))) + { + Result.PrevCursor = Json->GetStringField(TEXT("prev_cursor")); + } + if (Json->HasField(TEXT("cacheable_cursor"))) + { + Result.CacheableCursor = Json->GetStringField(TEXT("cacheable_cursor")); + } + return Result; +} + +TSharedPtr FSatoriGetMessageListResponse::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Messages.Num() > 0) + { + TArray> Array; + for (const auto& Item : Messages) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("messages"), Array); + } + if (!NextCursor.IsEmpty()) + { + Json->SetStringField(TEXT("next_cursor"), NextCursor); + } + if (!PrevCursor.IsEmpty()) + { + Json->SetStringField(TEXT("prev_cursor"), PrevCursor); + } + if (!CacheableCursor.IsEmpty()) + { + Json->SetStringField(TEXT("cacheable_cursor"), CacheableCursor); + } + return Json; +} + +FSatoriIdentifyRequest FSatoriIdentifyRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriIdentifyRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("default"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("default"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Default.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + if (Json->HasField(TEXT("custom"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("custom"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Custom.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriIdentifyRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("custom"), MapObj); + } + return Json; +} + +FSatoriJoinLiveEventRequest FSatoriJoinLiveEventRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriJoinLiveEventRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + return Result; +} + +TSharedPtr FSatoriJoinLiveEventRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + return Json; +} + +FSatoriLiveEvent FSatoriLiveEvent::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriLiveEvent Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("name"))) + { + Result.Name = Json->GetStringField(TEXT("name")); + } + if (Json->HasField(TEXT("description"))) + { + Result.Description = Json->GetStringField(TEXT("description")); + } + if (Json->HasField(TEXT("value"))) + { + Result.Value = Json->GetStringField(TEXT("value")); + } + if (Json->HasField(TEXT("active_start_time_sec"))) + { + Result.ActiveStartTimeSec = static_cast(Json->GetNumberField(TEXT("active_start_time_sec"))); + } + if (Json->HasField(TEXT("active_end_time_sec"))) + { + Result.ActiveEndTimeSec = static_cast(Json->GetNumberField(TEXT("active_end_time_sec"))); + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("start_time_sec"))) + { + Result.StartTimeSec = static_cast(Json->GetNumberField(TEXT("start_time_sec"))); + } + if (Json->HasField(TEXT("end_time_sec"))) + { + Result.EndTimeSec = static_cast(Json->GetNumberField(TEXT("end_time_sec"))); + } + if (Json->HasField(TEXT("duration_sec"))) + { + Result.DurationSec = static_cast(Json->GetNumberField(TEXT("duration_sec"))); + } + if (Json->HasField(TEXT("reset_cron"))) + { + Result.ResetCron = Json->GetStringField(TEXT("reset_cron")); + } + if (Json->HasField(TEXT("status"))) + { + Result.Status = static_cast(Json->GetIntegerField(TEXT("status"))); + } + if (Json->HasField(TEXT("labels"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("labels"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + Result.Labels.Add(Item->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriLiveEvent::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Name.IsEmpty()) + { + Json->SetStringField(TEXT("name"), Name); + } + if (!Description.IsEmpty()) + { + Json->SetStringField(TEXT("description"), Description); + } + if (!Value.IsEmpty()) + { + Json->SetStringField(TEXT("value"), Value); + } + Json->SetNumberField(TEXT("active_start_time_sec"), ActiveStartTimeSec); + Json->SetNumberField(TEXT("active_end_time_sec"), ActiveEndTimeSec); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + Json->SetNumberField(TEXT("start_time_sec"), StartTimeSec); + Json->SetNumberField(TEXT("end_time_sec"), EndTimeSec); + Json->SetNumberField(TEXT("duration_sec"), DurationSec); + if (!ResetCron.IsEmpty()) + { + Json->SetStringField(TEXT("reset_cron"), ResetCron); + } + Json->SetNumberField(TEXT("status"), static_cast(Status)); + if (Labels.Num() > 0) + { + TArray> Array; + for (const auto& Item : Labels) + { + Array.Add(MakeShared(Item)); + } + Json->SetArrayField(TEXT("labels"), Array); + } + return Json; +} + +FSatoriLiveEventList FSatoriLiveEventList::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriLiveEventList Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("live_events"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("live_events"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.LiveEvents.Add(FSatoriLiveEvent::FromJson(*ItemObj)); + } + } + } + } + if (Json->HasField(TEXT("explicit_join_live_events"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("explicit_join_live_events"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.ExplicitJoinLiveEvents.Add(FSatoriLiveEvent::FromJson(*ItemObj)); + } + } + } + } + return Result; +} + +TSharedPtr FSatoriLiveEventList::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (LiveEvents.Num() > 0) + { + TArray> Array; + for (const auto& Item : LiveEvents) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("live_events"), Array); + } + if (ExplicitJoinLiveEvents.Num() > 0) + { + TArray> Array; + for (const auto& Item : ExplicitJoinLiveEvents) + { + Array.Add(MakeShared(Item.ToJson())); + } + Json->SetArrayField(TEXT("explicit_join_live_events"), Array); + } + return Json; +} + +FSatoriProperties FSatoriProperties::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriProperties Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("default"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("default"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Default.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + if (Json->HasField(TEXT("computed"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("computed"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Computed.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + if (Json->HasField(TEXT("custom"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("custom"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Custom.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriProperties::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("default"), MapObj); + } + if (Computed.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Computed) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("computed"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("custom"), MapObj); + } + return Json; +} + +FSatoriSession FSatoriSession::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriSession Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("token"))) + { + Result.Token = Json->GetStringField(TEXT("token")); + } + if (Json->HasField(TEXT("refresh_token"))) + { + Result.RefreshToken = Json->GetStringField(TEXT("refresh_token")); + } + if (Json->HasField(TEXT("properties"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("properties"), NestedObj)) + { + Result.Properties = FSatoriProperties::FromJson(*NestedObj); + } + } + Result.ParseTokens(); + return Result; +} + +TSharedPtr FSatoriSession::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Token.IsEmpty()) + { + Json->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Json->SetStringField(TEXT("refresh_token"), RefreshToken); + } + Json->SetObjectField(TEXT("properties"), Properties.ToJson()); + return Json; +} + +FSatoriUpdatePropertiesRequest FSatoriUpdatePropertiesRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriUpdatePropertiesRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("recompute"))) + { + Result.Recompute = Json->GetBoolField(TEXT("recompute")); + } + if (Json->HasField(TEXT("default"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("default"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Default.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + if (Json->HasField(TEXT("custom"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("custom"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.Custom.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + return Result; +} + +TSharedPtr FSatoriUpdatePropertiesRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + Json->SetBoolField(TEXT("recompute"), Recompute); + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("custom"), MapObj); + } + return Json; +} + +FSatoriUpdateMessageRequest FSatoriUpdateMessageRequest::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriUpdateMessageRequest Result; + if (!Json.IsValid()) + { + return Result; + } + if (Json->HasField(TEXT("id"))) + { + Result.Id = Json->GetStringField(TEXT("id")); + } + if (Json->HasField(TEXT("read_time"))) + { + Result.ReadTime = static_cast(Json->GetNumberField(TEXT("read_time"))); + } + if (Json->HasField(TEXT("consume_time"))) + { + Result.ConsumeTime = static_cast(Json->GetNumberField(TEXT("consume_time"))); + } + return Result; +} + +TSharedPtr FSatoriUpdateMessageRequest::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); + if (!Id.IsEmpty()) + { + Json->SetStringField(TEXT("id"), Id); + } + Json->SetNumberField(TEXT("read_time"), ReadTime); + Json->SetNumberField(TEXT("consume_time"), ConsumeTime); + return Json; +} + +// --- FSatoriClientConfig --- + +FString FSatoriClientConfig::GetBaseUrl() const noexcept +{ + const FString Scheme = bUseSSL ? TEXT("https") : TEXT("http"); + return FString::Printf(TEXT("%s://%s:%d"), *Scheme, *Host, Port); +} + +void SatoriApi::Authenticate( + const FSatoriClientConfig& Config, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/authenticate"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Basic;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + Body->SetBoolField(TEXT("no_session"), NoSession); + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("custom"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FSatoriSession Result = FSatoriSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::AuthenticateLogout( + const FSatoriClientConfig& Config, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/authenticate/logout"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Token.IsEmpty()) + { + Body->SetStringField(TEXT("token"), Token); + } + if (!RefreshToken.IsEmpty()) + { + Body->SetStringField(TEXT("refresh_token"), RefreshToken); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::AuthenticateRefresh( + const FSatoriClientConfig& Config, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/authenticate/refresh"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Basic;TSharedPtr Body; + Body = MakeShared(); + if (!RefreshToken.IsEmpty()) + { + Body->SetStringField(TEXT("refresh_token"), RefreshToken); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, TEXT(""), + [OnSuccess](TSharedPtr Json) + { + FSatoriSession Result = FSatoriSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::DeleteIdentity( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/identity"); + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::Event( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Events, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/event"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (Events.Num() > 0) + { + TArray> Array; + for (const auto& Item : Events) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("events"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::ServerEvent( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Events, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/server-event"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (Events.Num() > 0) + { + TArray> Array; + for (const auto& Item : Events) + { + Array.Add(MakeShared(Item.ToJson())); + } + Body->SetArrayField(TEXT("events"), Array); + } + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::GetExperiments( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/experiment"); + TArray QueryParams; + for (const FString& Item : Names) + { + QueryParams.Add(TEXT("names=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Labels) + { + QueryParams.Add(TEXT("labels=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriExperimentList Result = FSatoriExperimentList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::GetFlagOverrides( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/flag/override"); + TArray QueryParams; + for (const FString& Item : Names) + { + QueryParams.Add(TEXT("names=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Labels) + { + QueryParams.Add(TEXT("labels=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriFlagOverrideList Result = FSatoriFlagOverrideList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::GetFlags( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/flag"); + TArray QueryParams; + for (const FString& Item : Names) + { + QueryParams.Add(TEXT("names=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Labels) + { + QueryParams.Add(TEXT("labels=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriFlagList Result = FSatoriFlagList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::GetLiveEvents( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/live-event"); + TArray QueryParams; + for (const FString& Item : Names) + { + QueryParams.Add(TEXT("names=") + FGenericPlatformHttp::UrlEncode(Item)); + } + for (const FString& Item : Labels) + { + QueryParams.Add(TEXT("labels=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (PastRunCount != 0) + { + QueryParams.Add(FString::Printf(TEXT("past_run_count=%d"), PastRunCount)); + } + if (FutureRunCount != 0) + { + QueryParams.Add(FString::Printf(TEXT("future_run_count=%d"), FutureRunCount)); + } + if (StartTimeSec != 0) + { + QueryParams.Add(FString::Printf(TEXT("start_time_sec=%lld"), StartTimeSec)); + } + if (EndTimeSec != 0) + { + QueryParams.Add(FString::Printf(TEXT("end_time_sec=%lld"), EndTimeSec)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriLiveEventList Result = FSatoriLiveEventList::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::JoinLiveEvent( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/live-event/{id}/participation"); + { const FString Encoded_Id = FGenericPlatformHttp::UrlEncode(Id); Endpoint = Endpoint.Replace(TEXT("{id}"), *Encoded_Id); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("POST"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::Healthcheck( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/healthcheck"); + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::Identify( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/identify"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("custom"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriSession Result = FSatoriSession::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::ListProperties( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/properties"); + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriProperties Result = FSatoriProperties::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::Readycheck( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/readycheck"); + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::UpdateProperties( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/properties"); + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + Body->SetBoolField(TEXT("recompute"), Recompute); + if (Default.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Default) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("default"), MapObj); + } + if (Custom.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Custom) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("custom"), MapObj); + } + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::GetMessageList( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/message"); + TArray QueryParams; + if (Limit != 0) + { + QueryParams.Add(FString::Printf(TEXT("limit=%d"), Limit)); + } + QueryParams.Add(FString::Printf(TEXT("forward=%s"), Forward ? TEXT("true") : TEXT("false"))); + if (!Cursor.IsEmpty()) + { + QueryParams.Add(TEXT("cursor=") + FGenericPlatformHttp::UrlEncode(Cursor)); + } + for (const FString& Item : MessageIds) + { + QueryParams.Add(TEXT("message_ids=") + FGenericPlatformHttp::UrlEncode(Item)); + } + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("GET"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + FSatoriGetMessageListResponse Result = FSatoriGetMessageListResponse::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::UpdateMessage( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/message/{id}"); + { const FString Encoded_Id = FGenericPlatformHttp::UrlEncode(Id); Endpoint = Endpoint.Replace(TEXT("{id}"), *Encoded_Id); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + Body = MakeShared(); + if (!Id.IsEmpty()) + { + Body->SetStringField(TEXT("id"), Id); + } + Body->SetNumberField(TEXT("read_time"), ReadTime); + Body->SetNumberField(TEXT("consume_time"), ConsumeTime); + + MakeRequest(Config, Endpoint, TEXT("PUT"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +void SatoriApi::DeleteMessage( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("/v1/message/{id}"); + { const FString Encoded_Id = FGenericPlatformHttp::UrlEncode(Id); Endpoint = Endpoint.Replace(TEXT("{id}"), *Encoded_Id); } + TArray QueryParams; + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + ESatoriRequestAuth AuthType = ESatoriRequestAuth::Bearer;TSharedPtr Body; + + MakeRequest(Config, Endpoint, TEXT("DELETE"), Body, AuthType, Session.Token, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + OnSuccess(); + } + }, + OnError, Timeout, CancellationToken); +} + +// Module implementation +class FSatoriApiModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogSatori, Log, TEXT("SatoriApi module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogSatori, Log, TEXT("SatoriApi module shutting down")); + } +}; + +IMPLEMENT_MODULE(FSatoriApiModule, SatoriApi) diff --git a/Satori/Source/SatoriApi/Public/AsyncFuture.h b/Satori/Source/SatoriApi/Public/AsyncFuture.h new file mode 100644 index 000000000..2e7976f96 --- /dev/null +++ b/Satori/Source/SatoriApi/Public/AsyncFuture.h @@ -0,0 +1,198 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "Tasks/Task.h" +#include "Async/TaskGraphInterfaces.h" +#include + +// Forward declaration +template struct TAsyncFuture; + +/** Type trait: is T a TAsyncFuture? */ +template struct TIsTAsyncFuture : std::false_type {}; +template struct TIsTAsyncFuture> : std::true_type {}; + +/** + * Chainable, game-thread-safe future for async operations. + * + * Parameterized by a concrete result type (e.g. FNakamaSessionResult or + * FSatoriSessionResult). Every user-visible callback is dispatched to the + * game thread via AsyncTask(ENamedThreads::GameThread, ...) so that callers + * can safely touch UObject*, fire delegates, or update UI without additional + * marshalling. + * + * Three .Next() overloads are provided: + * + * 1. Chaining with auto-propagation: + * Next(callback(const ValueType&) -> TAsyncFuture) + * Available only when ResultT defines ::ValueType. + * On error the error is forwarded to the outer future and the callback is + * skipped entirely (error propagation runs on the background thread because + * no user code is involved). + * + * 2. Chaining without auto-propagation: + * Next(callback(ResultT) -> TAsyncFuture) + * Available for all ResultT, including types without ::ValueType (e.g. + * WebSocket envelope types). The callback receives the full result and is + * responsible for inspecting bIsError and deciding what to return. + * + * 3. Terminal: + * Next(callback(ResultT) -> void) + * Called unconditionally when the future resolves. The caller inspects + * the result for errors inside the callback. + */ +template +struct TAsyncFuture +{ + using WrappedResultType = ResultT; + + struct FState + { + ResultT Result{}; + UE::Tasks::FTaskEvent Event{ UE_SOURCE_LOCATION }; + void Resolve(ResultT&& InResult) + { + Result = MoveTemp(InResult); + Event.Trigger(); + } + }; + + TSharedPtr State; + + TAsyncFuture() = default; + explicit TAsyncFuture(TSharedPtr InState) noexcept + : State(MoveTemp(InState)) {} + TAsyncFuture(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture& operator=(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture(const TAsyncFuture&) = delete; + TAsyncFuture& operator=(const TAsyncFuture&) = delete; + + /** + * Overload 1 — Chaining with auto-propagation. + * callback(const ValueType&) -> TAsyncFuture + * Only enabled when ResultT has ::ValueType. + * On error, propagates to OtherResult without calling the callback. + */ + template, const VT&>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + if (CapturedState->Result.bIsError) + { + // Error propagation: no user code involved, safe on any thread. + OuterState->Resolve(InnerResultT{{}, CapturedState->Result.Error, true}); + return; + } + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(CapturedState->Result.Value); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** + * Overload 2 — Chaining, user handles errors. + * callback(ResultT) -> TAsyncFuture + * Available for all ResultT, including WebSocket types without ::ValueType. + * The callback receives the full result and is responsible for error handling. + */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(MoveTemp(CapturedState->Result)); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** Overload 3 — Terminal. callback(ResultT) -> void */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + void Next(Func&& Callback) && noexcept + { + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState]() mutable + { + Cb(MoveTemp(CapturedState->Result)); + }); + }, + CapturedState->Event); + State.Reset(); + } +}; + +/** + * Create a pre-resolved TAsyncFuture. + * Useful for returning an immediate result (e.g. a local error) from a + * chaining callback without going through the network stack. + */ +template +TAsyncFuture MakeCompletedAsyncFuture(ResultT Value) +{ + auto State = MakeShared::FState>(); + State->Resolve(MoveTemp(Value)); + return TAsyncFuture(State); +} diff --git a/Satori/Source/SatoriApi/Public/SatoriApi.h b/Satori/Source/SatoriApi/Public/SatoriApi.h new file mode 100644 index 000000000..bd655039a --- /dev/null +++ b/Satori/Source/SatoriApi/Public/SatoriApi.h @@ -0,0 +1,973 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" + +#include "SatoriApi.generated.h" + +SATORIAPI_API DECLARE_LOG_CATEGORY_EXTERN(LogSatori, Log, All); + +// Forward declarations +struct FSatoriAuthenticateLogoutRequest; +struct FSatoriAuthenticateRefreshRequest; +struct FSatoriAuthenticateRequest; +struct FSatoriDeleteMessageRequest; +struct FSatoriEvent; +struct FSatoriEventRequest; +struct FSatoriExperiment; +struct FSatoriExperimentList; +struct FSatoriValueChangeReason; +struct FSatoriFlag; +struct FSatoriFlagList; +struct FSatoriFlagOverrideValue; +struct FSatoriFlagOverride; +struct FSatoriFlagOverrideList; +struct FSatoriGetExperimentsRequest; +struct FSatoriGetFlagsRequest; +struct FSatoriGetLiveEventsRequest; +struct FSatoriMessage; +struct FSatoriGetMessageListRequest; +struct FSatoriGetMessageListResponse; +struct FSatoriIdentifyRequest; +struct FSatoriJoinLiveEventRequest; +struct FSatoriLiveEvent; +struct FSatoriLiveEventList; +struct FSatoriProperties; +struct FSatoriSession; +struct FSatoriUpdatePropertiesRequest; +struct FSatoriUpdateMessageRequest; + +/** The type of configuration that overrides a flag value. */ +UENUM(BlueprintType) +enum class ESatoriValueChangeReasonType : uint8 +{ + VCR_UNKNOWN = 0, + VCR_FLAG_VARIANT = 1, + VCR_LIVE_EVENT = 2, + VCR_EXPERIMENT = 3 +}; + +/** The type of configuration that overrides a flag value. */ +UENUM(BlueprintType) +enum class ESatoriFlagOverrideType : uint8 +{ + FOT_FLAG = 0, + FOT_FLAG_VARIANT = 1, + FOT_LIVE_EVENT_FLAG = 2, + FOT_LIVE_EVENT_FLAG_VARIANT = 3, + FOT_EXPERIMENT_PHASE_VARIANT_FLAG = 4 +}; + +/** The status variants of a live event. */ +UENUM(BlueprintType) +enum class ESatoriLiveEventStatus : uint8 +{ + LES_UNKNOWN = 0, + LES_ACTIVE = 1, + LES_UPCOMING = 2, + LES_TERMINATED = 3 +}; + +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriError +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Message; + + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int32 Code = 0; + +}; +/** gRPC status codes returned by Satori in FSatoriError::Code. */ +namespace ESatoriErrorCode +{ + constexpr int32 OK = 0; + constexpr int32 Cancelled = 1; + constexpr int32 Unknown = 2; + constexpr int32 InvalidArgument = 3; + constexpr int32 DeadlineExceeded = 4; + constexpr int32 NotFound = 5; + constexpr int32 AlreadyExists = 6; + constexpr int32 PermissionDenied = 7; + constexpr int32 ResourceExhausted = 8; + constexpr int32 FailedPrecondition = 9; + constexpr int32 Aborted = 10; + constexpr int32 OutOfRange = 11; + constexpr int32 Unimplemented = 12; + constexpr int32 Internal = 13; + constexpr int32 Unavailable = 14; + constexpr int32 DataLoss = 15; + constexpr int32 Unauthenticated = 16; +} + +/** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriAuthenticateLogoutRequest +{ + GENERATED_BODY() + /** Session token to log out. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Token; + /** Refresh token to invalidate. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString RefreshToken; + + static FSatoriAuthenticateLogoutRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authenticate against the server with a refresh token. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriAuthenticateRefreshRequest +{ + GENERATED_BODY() + /** Refresh token. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString RefreshToken; + + static FSatoriAuthenticateRefreshRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Authentication request */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriAuthenticateRequest +{ + GENERATED_BODY() + /** Identity ID. Must be between eight and 128 characters (inclusive). */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** Optional no_session modifies the request to only create/update */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + bool NoSession = false; + /** Optional default properties to update with this call. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Default; + /** Optional custom properties to update with this call. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Custom; + + static FSatoriAuthenticateRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The request to delete a scheduled message. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriDeleteMessageRequest +{ + GENERATED_BODY() + /** The identifier of the message. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + + static FSatoriDeleteMessageRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A single event. Usually, but not necessarily, part of a batch. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriEvent +{ + GENERATED_BODY() + /** Event name. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** Optional event ID assigned by the client, used to de-duplicate in retransmission scenarios. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** Optional value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Value; + /** The time when the event was triggered on the producer side. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Timestamp; + /** The identity id associated with the event. Ignored if the event is published as part of a session. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString IdentityId; + /** The session id associated with the event. Ignored if the event is published as part of a session. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString SessionId; + /** The session issued at associated with the event. Ignored if the event is published as part of a session. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 SessionIssuedAt = 0; + /** The session expires at associated with the event. Ignored if the event is published as part of a session. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 SessionExpiresAt = 0; + /** Event metadata, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Metadata; + + static FSatoriEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Publish an event to the server */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriEventRequest +{ + GENERATED_BODY() + /** Some number of events produced by a client. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Events; + + static FSatoriEventRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** An experiment that this user is partaking. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriExperiment +{ + GENERATED_BODY() + /** Experiment name */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** Value associated with this Experiment. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Value; + /** The labels associated with this experiment. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + /** Experiment Phase name */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString PhaseName; + /** Experiment Phase Variant name */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString PhaseVariantName; + + static FSatoriExperiment FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** All experiments that this identity is involved with. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriExperimentList +{ + GENERATED_BODY() + /** All experiments for this identity. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Experiments; + + static FSatoriExperimentList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The origin of change on a flag value. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriValueChangeReason +{ + GENERATED_BODY() + /** The type of the configuration that declared the override. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + ESatoriValueChangeReasonType Type = static_cast(0); + /** The name of the configuration that overrides the flag value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** The variant name of the configuration that overrides the flag value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString VariantName; + + static FSatoriValueChangeReason FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Feature flag available to the identity. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriFlag +{ + GENERATED_BODY() + /** Flag name */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** Value associated with this flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Value; + /** Whether the value for this flag has conditionally changed from the default state. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + bool ConditionChanged = false; + /** The origin of change on the flag value returned. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FSatoriValueChangeReason ChangeReason; + /** The labels associated with this flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + + static FSatoriFlag FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** All flags available to the identity */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriFlagList +{ + GENERATED_BODY() + /** All flags */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Flags; + + static FSatoriFlagList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The details of a flag value override. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriFlagOverrideValue +{ + GENERATED_BODY() + /** The type of the configuration that declared the override. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + ESatoriFlagOverrideType Type = static_cast(0); + /** The name of the configuration that overrides the flag value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** The variant name of the configuration that overrides the flag value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString VariantName; + /** The value of the configuration that overrides the flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Value; + /** The create time of the configuration that overrides the flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 CreateTimeSec = 0; + + static FSatoriFlagOverrideValue FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Feature flag available to the identity. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriFlagOverride +{ + GENERATED_BODY() + /** Flag name */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString FlagName; + /** The list of configuration that affect the value of the flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Overrides; + /** The labels associated with this flag. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + + static FSatoriFlagOverride FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** All flags available to the identity and their value overrides */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriFlagOverrideList +{ + GENERATED_BODY() + /** All flags */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Flags; + + static FSatoriFlagOverrideList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Request to get all experiments data. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriGetExperimentsRequest +{ + GENERATED_BODY() + /** Experiment names; if empty string, all experiments are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Names; + /** Label names that must be defined for each Experiment; if empty string, all experiments are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + + static FSatoriGetExperimentsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Request to get all flags data. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriGetFlagsRequest +{ + GENERATED_BODY() + /** Flag names; if empty string, all flags are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Names; + /** Label names that must be defined for each Flag; if empty string, all flags are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + + static FSatoriGetFlagsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Request to get all live events. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriGetLiveEventsRequest +{ + GENERATED_BODY() + /** Live event names; if empty string, all live events are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Names; + /** Label names that must be defined for each Live Event; if empty string, all live events are returned based on the remaining filters. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + /** The maximum number of past event runs to return for each live event. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int32 PastRunCount = 0; + /** The maximum number of future event runs to return for each live event. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int32 FutureRunCount = 0; + /** Start time of the time window filter to apply. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 StartTimeSec = 0; + /** End time of the time window filter to apply. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 EndTimeSec = 0; + + static FSatoriGetLiveEventsRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A scheduled message. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriMessage +{ + GENERATED_BODY() + /** The identifier of the schedule. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString ScheduleId; + /** The send time for the message. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 SendTime = 0; + /** The time the message was created. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 CreateTime = 0; + /** The time the message was updated. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 UpdateTime = 0; + /** The time the message was read by the client. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ReadTime = 0; + /** The time the message was consumed by the identity. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ConsumeTime = 0; + /** The message's text. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Text; + /** The message's unique identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** The message's title. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Title; + /** The message's image url. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString ImageUrl; + /** A key-value pairs of metadata. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Metadata; + + static FSatoriMessage FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriGetMessageListRequest +{ + GENERATED_BODY() + /** Max number of messages to return. Between 1 and 100. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int32 Limit = 0; + /** True if listing should be older messages to newer, false if reverse. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + bool Forward = false; + /** A pagination cursor, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Cursor; + /** A list of message identifiers to be returned. If these are given as inputs, the pagination will be disabled. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray MessageIds; + + static FSatoriGetMessageListRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A response containing all the messages for an identity. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriGetMessageListResponse +{ + GENERATED_BODY() + /** The list of messages. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Messages; + /** The cursor to send when retrieving the next page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString NextCursor; + /** The cursor to send when retrieving the previous page, if any. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString PrevCursor; + /** Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString CacheableCursor; + + static FSatoriGetMessageListResponse FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Enrich/replace the current session with a new ID. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriIdentifyRequest +{ + GENERATED_BODY() + /** Identity ID to enrich the current session and return a new session. Old session will no longer be usable. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** Optional default properties to update with this call. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Default; + /** Optional custom properties to update with this call. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Custom; + + static FSatoriIdentifyRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Request to join a 'explicit join' live event. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriJoinLiveEventRequest +{ + GENERATED_BODY() + /** Live event id to join. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + + static FSatoriJoinLiveEventRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A single live event. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriLiveEvent +{ + GENERATED_BODY() + /** Name. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Name; + /** Description. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Description; + /** Event value. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Value; + /** Start time of current event run. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ActiveStartTimeSec = 0; + /** End time of current event run. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ActiveEndTimeSec = 0; + /** The live event identifier. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** Start time. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 StartTimeSec = 0; + /** End time, 0 if it repeats forever. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 EndTimeSec = 0; + /** Duration in seconds. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 DurationSec = 0; + /** Reset CRON schedule, if configured. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString ResetCron; + /** The status of this live event run. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + ESatoriLiveEventStatus Status = static_cast(0); + /** The labels associated with this live event. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray Labels; + + static FSatoriLiveEvent FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** List of Live events. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriLiveEventList +{ + GENERATED_BODY() + /** Live events. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray LiveEvents; + /** Live events that require explicit join. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TArray ExplicitJoinLiveEvents; + + static FSatoriLiveEventList FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** Properties associated with an identity. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriProperties +{ + GENERATED_BODY() + /** Event default properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Default; + /** Event computed properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Computed; + /** Event custom properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Custom; + + static FSatoriProperties FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** A session. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriSession +{ + GENERATED_BODY() + /** Token credential. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Token; + /** Refresh token. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString RefreshToken; + /** Properties associated with this identity. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FSatoriProperties Properties; + + /** Identity ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + FString IdentityId; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 RefreshTokenExpiresAt = 0; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static FSatoriSession FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; + +/** Update Properties associated with this identity. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriUpdatePropertiesRequest +{ + GENERATED_BODY() + /** Informs the server to recompute the audience membership of the identity. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + bool Recompute = false; + /** Event default properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Default; + /** Event custom properties. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + TMap Custom; + + static FSatoriUpdatePropertiesRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +/** The request to update the status of a message. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriUpdateMessageRequest +{ + GENERATED_BODY() + /** The identifier of the messages. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Id; + /** The time the message was read at the client. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ReadTime = 0; + /** The time the message was consumed by the identity. */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int64 ConsumeTime = 0; + + static FSatoriUpdateMessageRequest FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; + +enum class ESatoriRequestAuth : uint8 +{ + None, + Basic, + Bearer +}; + +/** Low-level API client configuration. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriClientConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + FString ServerKey; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + FString Host; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + int32 Port = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + bool bUseSSL = false; + + FString GetBaseUrl() const noexcept; +}; + +/** Low-level Satori API: data types + free functions for HTTP RPCs (callback-based). */ +namespace SatoriApi +{ + + /** Authenticate against the server. */ + SATORIAPI_API void Authenticate( + const FSatoriClientConfig& Config, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */ + SATORIAPI_API void AuthenticateLogout( + const FSatoriClientConfig& Config, + FString Token, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */ + SATORIAPI_API void AuthenticateRefresh( + const FSatoriClientConfig& Config, + FString RefreshToken, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Delete the caller's identity and associated data. */ + SATORIAPI_API void DeleteIdentity( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Publish an event for this session. */ + SATORIAPI_API void Event( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Events, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Publish server events for multiple distinct identities. */ + SATORIAPI_API void ServerEvent( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Events, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get or list all available experiments for this identity. */ + SATORIAPI_API void GetExperiments( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all available flags and their value overrides for this identity. */ + SATORIAPI_API void GetFlagOverrides( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List all available flags for this identity. */ + SATORIAPI_API void GetFlags( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List available live events. */ + SATORIAPI_API void GetLiveEvents( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Join an 'explicit join' live event. */ + SATORIAPI_API void JoinLiveEvent( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A healthcheck which load balancers can use to check the service. */ + SATORIAPI_API void Healthcheck( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Enrich/replace the current session with new identifier. */ + SATORIAPI_API void Identify( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** List properties associated with this identity. */ + SATORIAPI_API void ListProperties( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** A readycheck which load balancers can use to check the service. */ + SATORIAPI_API void Readycheck( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Update identity properties. */ + SATORIAPI_API void UpdateProperties( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Get the list of messages for the identity. */ + SATORIAPI_API void GetMessageList( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Updates a message for an identity. */ + SATORIAPI_API void UpdateMessage( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; + + /** Deletes a message for an identity. */ + SATORIAPI_API void DeleteMessage( + const FSatoriClientConfig& Config, + const FSatoriSession& Session, + FString Id, + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +} diff --git a/Satori/Source/SatoriApi/Public/SatoriHttpHelper.h b/Satori/Source/SatoriApi/Public/SatoriHttpHelper.h new file mode 100644 index 000000000..b298dcc42 --- /dev/null +++ b/Satori/Source/SatoriApi/Public/SatoriHttpHelper.h @@ -0,0 +1,232 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Dom/JsonObject.h" +#include "HttpModule.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Misc/Base64.h" +#include "Misc/DateTime.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/MemoryWriter.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "SatoriApi.h" + +/** + * Internal HTTP helpers for SatoriApi. + * All functions are typed against FSatoriClientConfig, ESatoriRequestAuth, and FSatoriError + * so SatoriApi does not depend on Nakama modules. + * + * Include this header in generated .cpp files only — not in public API headers. + */ +namespace SatoriHttpInternal +{ + +inline FString SerializeJsonToString(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(1024); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + return FString(Converter.Length(), Converter.Get()); +} + +inline FString SerializeJsonEscaped(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(2048); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + FString Condensed(Converter.Length(), Converter.Get()); + FString Escaped = Condensed.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")); + return TEXT("\"") + Escaped + TEXT("\""); +} + +inline void DoHttpRequest( + const FSatoriClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ESatoriRequestAuth AuthType, + const FString& TokenString, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + const FString Url = Config.GetBaseUrl() + Endpoint; + + TSharedRef Request = FHttpModule::Get().CreateRequest(); + Request->SetURL(Url); + Request->SetVerb(Method); + Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + Request->SetHeader(TEXT("Accept"), TEXT("application/json")); + + switch (AuthType) + { + case ESatoriRequestAuth::Basic: + { + const FString Auth = FString::Printf(TEXT("%s:"), *Config.ServerKey); + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Basic %s"), *FBase64::Encode(Auth))); + } + break; + case ESatoriRequestAuth::Bearer: + if (!TokenString.IsEmpty()) + { + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Bearer %s"), *TokenString)); + } + break; + case ESatoriRequestAuth::None: + default: + break; + } + + if (!BodyString.IsEmpty() && Method != TEXT("GET")) + { + Request->SetContentAsString(BodyString); + } + + Request->SetTimeout(Timeout); + + Request->OnProcessRequestComplete().BindLambda( + [OnSuccess, OnError, CancellationToken](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess) + { + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FSatoriError{TEXT("Request cancelled"), -1}); + } + return; + } + + if (!bSuccess || !Res.IsValid()) + { + if (OnError) + { + OnError(FSatoriError{TEXT("Connection failed"), 0}); + } + return; + } + + const int32 Code = Res->GetResponseCode(); + const FString Content = Res->GetContentAsString(); + + if (Code < 200 || Code >= 300) + { + FString ErrorMsg = FString::Printf(TEXT("HTTP %d"), Code); + int32 ErrorCode = Code; + TSharedPtr Json; + if (FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) && Json.IsValid()) + { + if (Json->HasField(TEXT("message"))) + { + ErrorMsg = Json->GetStringField(TEXT("message")); + } + if (Json->HasField(TEXT("code"))) + { + ErrorCode = static_cast(Json->GetNumberField(TEXT("code"))); + } + } + if (OnError) + { + OnError(FSatoriError{ErrorMsg, ErrorCode}); + } + return; + } + + TSharedPtr Json; + if (!Content.IsEmpty()) + { + if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) || !Json.IsValid()) + { + if (OnError) + { + OnError(FSatoriError{TEXT("Invalid JSON response"), 500}); + } + return; + } + } + + if (OnSuccess) + { + OnSuccess(Json); + } + }); + + Request->ProcessRequest(); +} + +inline void SendRequest( + const FSatoriClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ESatoriRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FSatoriError{TEXT("Request cancelled"), -1}); + } + return; + } + + DoHttpRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +inline void MakeRequest( + const FSatoriClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const TSharedPtr& Body, + ESatoriRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString BodyString; + if (Body.IsValid() && Method != TEXT("GET")) + { + BodyString = SerializeJsonToString(Body); + } + SendRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +} // namespace SatoriHttpInternal diff --git a/Nakama/Source/NakamaTests/NakamaTests.Build.cs b/Satori/Source/SatoriApi/SatoriApi.Build.cs similarity index 73% rename from Nakama/Source/NakamaTests/NakamaTests.Build.cs rename to Satori/Source/SatoriApi/SatoriApi.Build.cs index c996f8679..a2bfb160f 100644 --- a/Nakama/Source/NakamaTests/NakamaTests.Build.cs +++ b/Satori/Source/SatoriApi/SatoriApi.Build.cs @@ -15,10 +15,11 @@ */ using UnrealBuildTool; +using System.IO; -public class NakamaTests : ModuleRules +public class SatoriApi : ModuleRules { - public NakamaTests(ReadOnlyTargetRules Target) : base(Target) + public SatoriApi(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; @@ -39,10 +40,7 @@ public NakamaTests(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "HTTP", - "Core", - "NakamaUnreal", - "FunctionalTesting" + "Core", "HTTP", "JsonUtilities" // ... add other public dependencies that you statically link with here ... } ); @@ -51,15 +49,15 @@ public NakamaTests(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.AddRange( new string[] { - "CoreUObject", + "CoreUObject", "Engine", "Slate", "SlateCore", "Engine", "JsonUtilities", "Json", - - // ... private dependencies that you statically link with here ... + "HTTP" + // ... add private dependencies that you statically link with here ... } ); @@ -70,5 +68,10 @@ public NakamaTests(ReadOnlyTargetRules Target) : base(Target) // ... add any modules that your module loads dynamically here ... } ); - } + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + } } diff --git a/Satori/Source/SatoriBlueprints/Private/SatoriBlueprint.cpp b/Satori/Source/SatoriBlueprints/Private/SatoriBlueprint.cpp deleted file mode 100644 index e45849634..000000000 --- a/Satori/Source/SatoriBlueprints/Private/SatoriBlueprint.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriBlueprints.h" -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE "FSatoriBlueprintsModule" - -DEFINE_LOG_CATEGORY(LogSatoriBlueprints); - - -void FSatoriBlueprintsModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - - //NLogger::init(std::make_shared(), NLogLevel::Debug); - -} - -void FSatoriBlueprintsModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FSatoriBlueprintsModule, SatoriBlueprints) diff --git a/Satori/Source/SatoriBlueprints/Private/SatoriBlueprintsModule.cpp b/Satori/Source/SatoriBlueprints/Private/SatoriBlueprintsModule.cpp new file mode 100644 index 000000000..249fc9587 --- /dev/null +++ b/Satori/Source/SatoriBlueprints/Private/SatoriBlueprintsModule.cpp @@ -0,0 +1,7 @@ +#include "Modules/ModuleManager.h" + +class FSatoriBlueprintsModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FSatoriBlueprintsModule, SatoriBlueprints) diff --git a/Satori/Source/SatoriBlueprints/Private/SatoriClientBlueprintLibrary.cpp b/Satori/Source/SatoriBlueprints/Private/SatoriClientBlueprintLibrary.cpp new file mode 100644 index 000000000..33f220b49 --- /dev/null +++ b/Satori/Source/SatoriBlueprints/Private/SatoriClientBlueprintLibrary.cpp @@ -0,0 +1,920 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "SatoriClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ + +// Authenticate +USatoriClientAuthenticate* USatoriClientAuthenticate::Authenticate( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom) +{ + USatoriClientAuthenticate* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredId = Id; + Action->StoredNoSession = NoSession; + Action->StoredDefault = Default; + Action->StoredCustom = Custom; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientAuthenticate::Activate() +{ + static const TCHAR* TraceScope_Authenticate = TEXT("SatoriBP_Authenticate"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Authenticate); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::Authenticate( + Client, + StoredId, + StoredNoSession, + StoredDefault, + StoredCustom, + [WeakThis](const FSatoriSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateLogout +USatoriClientAuthenticateLogout* USatoriClientAuthenticateLogout::AuthenticateLogout( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString Token, + FString RefreshToken) +{ + USatoriClientAuthenticateLogout* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredToken = Token; + Action->StoredRefreshToken = RefreshToken; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientAuthenticateLogout::Activate() +{ + static const TCHAR* TraceScope_AuthenticateLogout = TEXT("SatoriBP_AuthenticateLogout"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateLogout); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::AuthenticateLogout( + Client, + StoredToken, + StoredRefreshToken, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// AuthenticateRefresh +USatoriClientAuthenticateRefresh* USatoriClientAuthenticateRefresh::AuthenticateRefresh( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString RefreshToken) +{ + USatoriClientAuthenticateRefresh* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->StoredRefreshToken = RefreshToken; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientAuthenticateRefresh::Activate() +{ + static const TCHAR* TraceScope_AuthenticateRefresh = TEXT("SatoriBP_AuthenticateRefresh"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_AuthenticateRefresh); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::AuthenticateRefresh( + Client, + StoredRefreshToken, + [WeakThis](const FSatoriSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteIdentity +USatoriClientDeleteIdentity* USatoriClientDeleteIdentity::DeleteIdentity( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session) +{ + USatoriClientDeleteIdentity* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientDeleteIdentity::Activate() +{ + static const TCHAR* TraceScope_DeleteIdentity = TEXT("SatoriBP_DeleteIdentity"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteIdentity); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::DeleteIdentity( + Client, + Session, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Event +USatoriClientEvent* USatoriClientEvent::Event( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Events) +{ + USatoriClientEvent* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredEvents = Events; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientEvent::Activate() +{ + static const TCHAR* TraceScope_Event = TEXT("SatoriBP_Event"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Event); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::Event( + Client, + Session, + StoredEvents, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ServerEvent +USatoriClientServerEvent* USatoriClientServerEvent::ServerEvent( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Events) +{ + USatoriClientServerEvent* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredEvents = Events; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientServerEvent::Activate() +{ + static const TCHAR* TraceScope_ServerEvent = TEXT("SatoriBP_ServerEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ServerEvent); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::ServerEvent( + Client, + Session, + StoredEvents, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetExperiments +USatoriClientGetExperiments* USatoriClientGetExperiments::GetExperiments( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels) +{ + USatoriClientGetExperiments* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredNames = Names; + Action->StoredLabels = Labels; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientGetExperiments::Activate() +{ + static const TCHAR* TraceScope_GetExperiments = TEXT("SatoriBP_GetExperiments"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetExperiments); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::GetExperiments( + Client, + Session, + StoredNames, + StoredLabels, + [WeakThis](const FSatoriExperimentList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetFlagOverrides +USatoriClientGetFlagOverrides* USatoriClientGetFlagOverrides::GetFlagOverrides( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels) +{ + USatoriClientGetFlagOverrides* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredNames = Names; + Action->StoredLabels = Labels; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientGetFlagOverrides::Activate() +{ + static const TCHAR* TraceScope_GetFlagOverrides = TEXT("SatoriBP_GetFlagOverrides"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetFlagOverrides); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::GetFlagOverrides( + Client, + Session, + StoredNames, + StoredLabels, + [WeakThis](const FSatoriFlagOverrideList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetFlags +USatoriClientGetFlags* USatoriClientGetFlags::GetFlags( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels) +{ + USatoriClientGetFlags* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredNames = Names; + Action->StoredLabels = Labels; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientGetFlags::Activate() +{ + static const TCHAR* TraceScope_GetFlags = TEXT("SatoriBP_GetFlags"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetFlags); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::GetFlags( + Client, + Session, + StoredNames, + StoredLabels, + [WeakThis](const FSatoriFlagList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetLiveEvents +USatoriClientGetLiveEvents* USatoriClientGetLiveEvents::GetLiveEvents( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec) +{ + USatoriClientGetLiveEvents* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredNames = Names; + Action->StoredLabels = Labels; + Action->StoredPastRunCount = PastRunCount; + Action->StoredFutureRunCount = FutureRunCount; + Action->StoredStartTimeSec = StartTimeSec; + Action->StoredEndTimeSec = EndTimeSec; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientGetLiveEvents::Activate() +{ + static const TCHAR* TraceScope_GetLiveEvents = TEXT("SatoriBP_GetLiveEvents"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetLiveEvents); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::GetLiveEvents( + Client, + Session, + StoredNames, + StoredLabels, + StoredPastRunCount, + StoredFutureRunCount, + StoredStartTimeSec, + StoredEndTimeSec, + [WeakThis](const FSatoriLiveEventList& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// JoinLiveEvent +USatoriClientJoinLiveEvent* USatoriClientJoinLiveEvent::JoinLiveEvent( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id) +{ + USatoriClientJoinLiveEvent* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientJoinLiveEvent::Activate() +{ + static const TCHAR* TraceScope_JoinLiveEvent = TEXT("SatoriBP_JoinLiveEvent"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_JoinLiveEvent); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::JoinLiveEvent( + Client, + Session, + StoredId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Healthcheck +USatoriClientHealthcheck* USatoriClientHealthcheck::Healthcheck( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session) +{ + USatoriClientHealthcheck* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientHealthcheck::Activate() +{ + static const TCHAR* TraceScope_Healthcheck = TEXT("SatoriBP_Healthcheck"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Healthcheck); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::Healthcheck( + Client, + Session, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Identify +USatoriClientIdentify* USatoriClientIdentify::Identify( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom) +{ + USatoriClientIdentify* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredDefault = Default; + Action->StoredCustom = Custom; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientIdentify::Activate() +{ + static const TCHAR* TraceScope_Identify = TEXT("SatoriBP_Identify"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Identify); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::Identify( + Client, + Session, + StoredId, + StoredDefault, + StoredCustom, + [WeakThis](const FSatoriSession& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// ListProperties +USatoriClientListProperties* USatoriClientListProperties::ListProperties( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session) +{ + USatoriClientListProperties* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientListProperties::Activate() +{ + static const TCHAR* TraceScope_ListProperties = TEXT("SatoriBP_ListProperties"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_ListProperties); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::ListProperties( + Client, + Session, + [WeakThis](const FSatoriProperties& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// Readycheck +USatoriClientReadycheck* USatoriClientReadycheck::Readycheck( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session) +{ + USatoriClientReadycheck* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientReadycheck::Activate() +{ + static const TCHAR* TraceScope_Readycheck = TEXT("SatoriBP_Readycheck"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_Readycheck); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::Readycheck( + Client, + Session, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UpdateProperties +USatoriClientUpdateProperties* USatoriClientUpdateProperties::UpdateProperties( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom) +{ + USatoriClientUpdateProperties* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredRecompute = Recompute; + Action->StoredDefault = Default; + Action->StoredCustom = Custom; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientUpdateProperties::Activate() +{ + static const TCHAR* TraceScope_UpdateProperties = TEXT("SatoriBP_UpdateProperties"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UpdateProperties); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::UpdateProperties( + Client, + Session, + StoredRecompute, + StoredDefault, + StoredCustom, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// GetMessageList +USatoriClientGetMessageList* USatoriClientGetMessageList::GetMessageList( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds) +{ + USatoriClientGetMessageList* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredLimit = Limit; + Action->StoredForward = Forward; + Action->StoredCursor = Cursor; + Action->StoredMessageIds = MessageIds; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientGetMessageList::Activate() +{ + static const TCHAR* TraceScope_GetMessageList = TEXT("SatoriBP_GetMessageList"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_GetMessageList); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::GetMessageList( + Client, + Session, + StoredLimit, + StoredForward, + StoredCursor, + StoredMessageIds, + [WeakThis](const FSatoriGetMessageListResponse& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// UpdateMessage +USatoriClientUpdateMessage* USatoriClientUpdateMessage::UpdateMessage( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime) +{ + USatoriClientUpdateMessage* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->StoredReadTime = ReadTime; + Action->StoredConsumeTime = ConsumeTime; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientUpdateMessage::Activate() +{ + static const TCHAR* TraceScope_UpdateMessage = TEXT("SatoriBP_UpdateMessage"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_UpdateMessage); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::UpdateMessage( + Client, + Session, + StoredId, + StoredReadTime, + StoredConsumeTime, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} + +// DeleteMessage +USatoriClientDeleteMessage* USatoriClientDeleteMessage::DeleteMessage( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id) +{ + USatoriClientDeleteMessage* Action = NewObject(GetTransientPackage()); + Action->Client = Client; + Action->Session = Session; + Action->StoredId = Id; + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClientDeleteMessage::Activate() +{ + static const TCHAR* TraceScope_DeleteMessage = TEXT("SatoriBP_DeleteMessage"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_DeleteMessage); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::DeleteMessage( + Client, + Session, + StoredId, + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} diff --git a/Satori/Source/SatoriBlueprints/Private/SatoriClientRequests.cpp b/Satori/Source/SatoriBlueprints/Private/SatoriClientRequests.cpp deleted file mode 100644 index acad505ea..000000000 --- a/Satori/Source/SatoriBlueprints/Private/SatoriClientRequests.cpp +++ /dev/null @@ -1,992 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriClientRequests.h" -#include "SatoriUtils.h" - -USatoriClientAuthenticate* USatoriClientAuthenticate::Authenticate( - USatoriClient* Client, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession) -{ - USatoriClientAuthenticate* Node = NewObject(); - Node->SatoriClient = Client; - Node->ID = ID; - Node->DefaultProperties = DefaultProperties; - Node->CustomProperties = CustomProperties; - Node->bNoSession = bNoSession; - - return Node; -} - -void USatoriClientAuthenticate::Activate() -{ - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](USatoriSession* session) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(session, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->Authenticate(ID, DefaultProperties, CustomProperties, bNoSession, successCallback, errorCallback); -} - -USatoriClientAuthenticateRefresh* USatoriClientAuthenticateRefresh::AuthenticateRefresh(USatoriClient* Client, USatoriSession* Session) -{ - USatoriClientAuthenticateRefresh* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - - return Node; -} - -void USatoriClientAuthenticateRefresh::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](USatoriSession* session) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(session, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->AuthenticateRefresh(UserSession, successCallback, errorCallback); -} - -USatoriClientAuthenticateLogout* USatoriClientAuthenticateLogout::AuthenticateLogout(USatoriClient* Client, USatoriSession* Session) -{ - USatoriClientAuthenticateLogout* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - - return Node; -} - -void USatoriClientAuthenticateLogout::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->AuthenticateLogout(UserSession, successCallback, errorCallback); -} - -USatoriClientIdentify* USatoriClientIdentify::Identify( - USatoriClient* Client, - USatoriSession* Session, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties) -{ - USatoriClientIdentify* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->ID = ID; - Node->DefaultProperties = DefaultProperties; - Node->CustomProperties = CustomProperties; - - return Node; -} - -void USatoriClientIdentify::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](USatoriSession* session) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(session, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->Identify(UserSession, ID, DefaultProperties, CustomProperties, successCallback, errorCallback); -} - -USatoriClientListIdentityProperties* USatoriClientListIdentityProperties::ListIdentityProperties(USatoriClient* Client, USatoriSession* Session) -{ - USatoriClientListIdentityProperties* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - - return Node; -} - -void USatoriClientListIdentityProperties::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriProperties& Properties) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(Properties, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->ListIdentityProperties(UserSession, successCallback, errorCallback); -} - -USatoriClientUpdateProperties* USatoriClientUpdateProperties::UpdateProperties( - USatoriClient* Client, - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute) -{ - USatoriClientUpdateProperties* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->DefaultProperties = DefaultProperties; - Node->CustomProperties = CustomProperties; - Node->bRecompute = bRecompute; - - return Node; -} - -void USatoriClientUpdateProperties::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->UpdateProperties(UserSession, DefaultProperties, CustomProperties, bRecompute, successCallback, errorCallback); -} - -USatoriClientDeleteIdentity* USatoriClientDeleteIdentity::DeleteIdentity(USatoriClient* Client, USatoriSession* Session) -{ - USatoriClientDeleteIdentity* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - - return Node; -} - -void USatoriClientDeleteIdentity::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->DeleteIdentity(UserSession, successCallback, errorCallback); -} - -USatoriClientPostEvent* USatoriClientPostEvent::PostEvent(USatoriClient* Client, USatoriSession* Session, const TArray& Events) -{ - USatoriClientPostEvent* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->Events = Events; - - return Node; -} - -void USatoriClientPostEvent::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->PostEvent(UserSession, Events, successCallback, errorCallback); -} - -USatoriClientGetExperiments* USatoriClientGetExperiments::GetExperiments(USatoriClient* Client, USatoriSession* Session, const TArray& Names) -{ - USatoriClientGetExperiments* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->Names = Names; - - return Node; -} - -void USatoriClientGetExperiments::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriExperimentList& Experiments) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(Experiments, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->GetExperiments(UserSession, Names, successCallback, errorCallback); -} - -USatoriClientGetFlags* USatoriClientGetFlags::GetFlags(USatoriClient* Client, USatoriSession* Session, const TArray& Names) -{ - USatoriClientGetFlags* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->Names = Names; - - return Node; -} - -void USatoriClientGetFlags::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriFlagList& Flags) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(Flags, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->GetFlags(UserSession, Names, successCallback, errorCallback); -} - -USatoriClientGetFlagOverrides* USatoriClientGetFlagOverrides::GetFlagOverrides(USatoriClient* Client, USatoriSession* Session, const TArray& Names) -{ - USatoriClientGetFlagOverrides* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->Names = Names; - - return Node; -} - -void USatoriClientGetFlagOverrides::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriFlagOverrideList& FlagOverrides) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(FlagOverrides, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->GetFlagOverrides(UserSession, Names, successCallback, errorCallback); -} - -USatoriClientGetLiveEvents* USatoriClientGetLiveEvents::GetLiveEvents(USatoriClient* Client, USatoriSession* Session, const TArray& LiveEventNames) -{ - USatoriClientGetLiveEvents* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->LiveEventNames = LiveEventNames; - - return Node; -} - -void USatoriClientGetLiveEvents::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriLiveEventList& LiveEvents) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(LiveEvents, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->GetLiveEvents(UserSession, LiveEventNames, successCallback, errorCallback); -} - -USatoriClientGetMessages* USatoriClientGetMessages::GetMessages( - USatoriClient* Client, - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor) -{ - USatoriClientGetMessages* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->Limit = Limit; - Node->Forward = Forward; - Node->Cursor = Cursor; - - return Node; -} - -void USatoriClientGetMessages::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast({}, Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this](const FSatoriMessageList& Messages) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast(Messages, {}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast({}, error); - SetReadyToDestroy(); - }; - - SatoriClient->GetMessages(UserSession, Limit, Forward, Cursor, successCallback, errorCallback); -} - -USatoriClientUpdateMessage* USatoriClientUpdateMessage::UpdateMessage( - USatoriClient* Client, - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime) -{ - USatoriClientUpdateMessage* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->MessageId = MessageId; - Node->ReadTime = ReadTime; - Node->ConsumeTime = ConsumeTime; - - return Node; -} - -void USatoriClientUpdateMessage::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->UpdateMessage(UserSession, MessageId, ReadTime, ConsumeTime, successCallback, errorCallback); -} - -USatoriClientDeleteMessage* USatoriClientDeleteMessage::DeleteMessage(USatoriClient* Client, USatoriSession* Session, const FString& MessageId) -{ - USatoriClientDeleteMessage* Node = NewObject(); - Node->SatoriClient = Client; - Node->UserSession = Session; - Node->MessageId = MessageId; - - return Node; -} - -void USatoriClientDeleteMessage::Activate() -{ - // Check validity of client and session - if (!SatoriClient && !UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClientAndSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!SatoriClient) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidClient(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - if (!UserSession) - { - const FSatoriError Error = FSatoriUtils::HandleInvalidSession(); - OnError.Broadcast(Error); - SetReadyToDestroy(); - return; - } - - auto successCallback = [this]() - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnSuccess.Broadcast({}); - SetReadyToDestroy(); - }; - - auto errorCallback = [this](const FSatoriError& error) - { - if (!USatoriClient::IsClientActive(SatoriClient)) - { - SetReadyToDestroy(); - return; - } - - OnError.Broadcast(error); - SetReadyToDestroy(); - }; - - SatoriClient->DeleteMessage(UserSession, MessageId, successCallback, errorCallback); -} diff --git a/Satori/Source/SatoriBlueprints/Public/SatoriBlueprints.h b/Satori/Source/SatoriBlueprints/Public/SatoriBlueprints.h deleted file mode 100644 index 9ff6aed95..000000000 --- a/Satori/Source/SatoriBlueprints/Public/SatoriBlueprints.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogSatoriBlueprints, Log, All); - - -class FSatoriBlueprintsModule : public IModuleInterface -{ -public: - - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; - -}; diff --git a/Satori/Source/SatoriBlueprints/Public/SatoriClientBlueprintLibrary.h b/Satori/Source/SatoriBlueprints/Public/SatoriClientBlueprintLibrary.h new file mode 100644 index 000000000..cc2fe0035 --- /dev/null +++ b/Satori/Source/SatoriBlueprints/Public/SatoriClientBlueprintLibrary.h @@ -0,0 +1,642 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "SatoriApi.h" + +#include "SatoriClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriError, const FSatoriError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSatoriSuccess); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriSession, const FSatoriSession&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriExperimentList, const FSatoriExperimentList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriFlagOverrideList, const FSatoriFlagOverrideList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriFlagList, const FSatoriFlagList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriLiveEventList, const FSatoriLiveEventList&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriProperties, const FSatoriProperties&, Result); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriGetMessageListResponse, const FSatoriGetMessageListResponse&, Result); + +// ============================================================================ +// Async Action Classes (one per RPC) +// ============================================================================ + +/** + * Authenticate against the server. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientAuthenticate : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Authenticate"), Category = "Satori|Client") + static USatoriClientAuthenticate* Authenticate( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString Id, + bool NoSession, + const TMap& Default, + const TMap& Custom); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FString StoredId; + bool StoredNoSession = false; + TMap StoredDefault; + TMap StoredCustom; +}; + +/** + * Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientAuthenticateLogout : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateLogout"), Category = "Satori|Client") + static USatoriClientAuthenticateLogout* AuthenticateLogout( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString Token, + FString RefreshToken); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FString StoredToken; + FString StoredRefreshToken; +}; + +/** + * Refresh a user's session using a refresh token retrieved from a previous authentication request. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientAuthenticateRefresh : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "AuthenticateRefresh"), Category = "Satori|Client") + static USatoriClientAuthenticateRefresh* AuthenticateRefresh( + UObject* WorldContextObject, + FSatoriClientConfig Client, + FString RefreshToken); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FString StoredRefreshToken; +}; + +/** + * Delete the caller's identity and associated data. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientDeleteIdentity : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteIdentity"), Category = "Satori|Client") + static USatoriClientDeleteIdentity* DeleteIdentity( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; +}; + +/** + * Publish an event for this session. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Event"), Category = "Satori|Client") + static USatoriClientEvent* Event( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Events); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredEvents; +}; + +/** + * Publish server events for multiple distinct identities. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientServerEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ServerEvent"), Category = "Satori|Client") + static USatoriClientServerEvent* ServerEvent( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Events); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredEvents; +}; + +/** + * Get or list all available experiments for this identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientGetExperiments : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriExperimentList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetExperiments"), Category = "Satori|Client") + static USatoriClientGetExperiments* GetExperiments( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredNames; + TArray StoredLabels; +}; + +/** + * List all available flags and their value overrides for this identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientGetFlagOverrides : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriFlagOverrideList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetFlagOverrides"), Category = "Satori|Client") + static USatoriClientGetFlagOverrides* GetFlagOverrides( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredNames; + TArray StoredLabels; +}; + +/** + * List all available flags for this identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientGetFlags : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriFlagList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetFlags"), Category = "Satori|Client") + static USatoriClientGetFlags* GetFlags( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredNames; + TArray StoredLabels; +}; + +/** + * List available live events. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientGetLiveEvents : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriLiveEventList OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetLiveEvents"), Category = "Satori|Client") + static USatoriClientGetLiveEvents* GetLiveEvents( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + const TArray& Names, + const TArray& Labels, + int32 PastRunCount, + int32 FutureRunCount, + int64 StartTimeSec, + int64 EndTimeSec); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + TArray StoredNames; + TArray StoredLabels; + int32 StoredPastRunCount = 0; + int32 StoredFutureRunCount = 0; + int64 StoredStartTimeSec = 0; + int64 StoredEndTimeSec = 0; +}; + +/** + * Join an 'explicit join' live event. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientJoinLiveEvent : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "JoinLiveEvent"), Category = "Satori|Client") + static USatoriClientJoinLiveEvent* JoinLiveEvent( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + FString StoredId; +}; + +/** + * A healthcheck which load balancers can use to check the service. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientHealthcheck : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Healthcheck"), Category = "Satori|Client") + static USatoriClientHealthcheck* Healthcheck( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; +}; + +/** + * Enrich/replace the current session with new identifier. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientIdentify : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSession OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Identify"), Category = "Satori|Client") + static USatoriClientIdentify* Identify( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id, + const TMap& Default, + const TMap& Custom); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + FString StoredId; + TMap StoredDefault; + TMap StoredCustom; +}; + +/** + * List properties associated with this identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientListProperties : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriProperties OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "ListProperties"), Category = "Satori|Client") + static USatoriClientListProperties* ListProperties( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; +}; + +/** + * A readycheck which load balancers can use to check the service. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientReadycheck : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Readycheck"), Category = "Satori|Client") + static USatoriClientReadycheck* Readycheck( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; +}; + +/** + * Update identity properties. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientUpdateProperties : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UpdateProperties"), Category = "Satori|Client") + static USatoriClientUpdateProperties* UpdateProperties( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + bool Recompute, + const TMap& Default, + const TMap& Custom); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + bool StoredRecompute = false; + TMap StoredDefault; + TMap StoredCustom; +}; + +/** + * Get the list of messages for the identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientGetMessageList : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriGetMessageListResponse OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "GetMessageList"), Category = "Satori|Client") + static USatoriClientGetMessageList* GetMessageList( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + int32 Limit, + bool Forward, + FString Cursor, + const TArray& MessageIds); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + int32 StoredLimit = 0; + bool StoredForward = false; + FString StoredCursor; + TArray StoredMessageIds; +}; + +/** + * Updates a message for an identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientUpdateMessage : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "UpdateMessage"), Category = "Satori|Client") + static USatoriClientUpdateMessage* UpdateMessage( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id, + int64 ReadTime, + int64 ConsumeTime); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + FString StoredId; + int64 StoredReadTime = 0; + int64 StoredConsumeTime = 0; +}; + +/** + * Deletes a message for an identity. + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClientDeleteMessage : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "DeleteMessage"), Category = "Satori|Client") + static USatoriClientDeleteMessage* DeleteMessage( + UObject* WorldContextObject, + FSatoriClientConfig Client, + const FSatoriSession& Session, + FString Id); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + FSatoriSession Session; + FString StoredId; +}; diff --git a/Satori/Source/SatoriBlueprints/Public/SatoriClientRequests.h b/Satori/Source/SatoriBlueprints/Public/SatoriClientRequests.h deleted file mode 100644 index 4f9552eb0..000000000 --- a/Satori/Source/SatoriBlueprints/Public/SatoriClientRequests.h +++ /dev/null @@ -1,670 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriClient.h" -#include "Kismet/BlueprintAsyncActionBase.h" -#include "SatoriError.h" - -#include "SatoriClientRequests.generated.h" - -// Delegates -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriAuthenticate, USatoriSession*, Session, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriAuthenticateRefresh, USatoriSession*, Session, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriAuthenticateLogout, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriIdentify, USatoriSession*, Session, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriListIdentityProperties, FSatoriProperties, Properties, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriUpdateProperties, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriDeleteIdentity, FSatoriError, Error); - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriPostEvent, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriGetExperiments, FSatoriExperimentList, Experiments, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriGetFlags, FSatoriFlagList, Flags, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriGetFlagOverrides, FSatoriFlagOverrideList, FlagOverrides, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriGetLiveEvents, FSatoriLiveEventList, LiveEvents, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSatoriGetMessages, FSatoriMessageList, Messages, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriUpdateMessage, FSatoriError, Error); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriDeleteMessage, FSatoriError, Error); - - - -// --- Authentication --- // - - -/** - * Authenticate Custom - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientAuthenticate : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticate OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticate OnError; - - /** - * Authenticate to get a satori session. - * @param ID Must be between eight and 128 characters (inclusive). Must be an alphanumeric string with only underscores and hyphens allowed. - * @param DefaultProperties Optional default properties to update with this call. If not set, properties are left as they are on the server. - * @param CustomProperties Optional custom properties to update with this call. If not set, properties are left as they are on the server. - * @param bNoSession Modifies the request to only create/update an identity without creating a new session. If set to 'true' the response won't include a token and a refresh token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "DefaultProperties,CustomProperties")) - static USatoriClientAuthenticate* Authenticate( - USatoriClient *Client, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession); - - virtual void Activate() override; - -private: - - FString ID; - TMap DefaultProperties; - TMap CustomProperties; - bool bNoSession; -}; - - -/** - * Refresh authentication - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientAuthenticateRefresh : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticateRefresh OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticateRefresh OnError; - - /** - * Post one or more events. - * - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientAuthenticateRefresh* AuthenticateRefresh(USatoriClient* Client, USatoriSession* Session); - - virtual void Activate() override; -}; - - -/** - * Logout - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientAuthenticateLogout : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticateLogout OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriAuthenticateLogout OnError; - - /** - * Post one or more events. - * - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientAuthenticateLogout* AuthenticateLogout(USatoriClient* Client, USatoriSession* Session); - - virtual void Activate() override; -}; - - -/** - * Identify - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientIdentify : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriIdentify OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriIdentify OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "DefaultProperties,CustomProperties")) - static USatoriClientIdentify* Identify( - USatoriClient* Client, - USatoriSession* Session, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties); - - virtual void Activate() override; - -private: - FString ID; - TMap DefaultProperties; - TMap CustomProperties; -}; - - -/** - * List identity properties - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientListIdentityProperties : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriListIdentityProperties OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriListIdentityProperties OnError; - - /** - * Post one or more events. - * - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientListIdentityProperties* ListIdentityProperties(USatoriClient* Client, USatoriSession* Session); - - virtual void Activate() override; -}; - - -/** - * Update properties - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientUpdateProperties : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriUpdateProperties OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriUpdateProperties OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "DefaultProperties,CustomProperties")) - static USatoriClientUpdateProperties* UpdateProperties( - USatoriClient* Client, - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute); - - virtual void Activate() override; - -private: - TMap DefaultProperties; - TMap CustomProperties; - bool bRecompute; -}; - - -/** - * Delete identity - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientDeleteIdentity : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriDeleteIdentity OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriDeleteIdentity OnError; - - /** - * Post one or more events. - * - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientDeleteIdentity* DeleteIdentity(USatoriClient* Client, USatoriSession* Session); - - virtual void Activate() override; -}; - - - -// --- Interface --- // - - -/** - * Send event - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientPostEvent : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriPostEvent OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriPostEvent OnError; - - /** - * Post one or more events. - * - * @param Events The events to publish. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Events", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientPostEvent* PostEvent(USatoriClient* Client, USatoriSession* Session, const TArray& Events); - - virtual void Activate() override; - -private: - TArray Events; -}; - - -/** - * Get experiments - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientGetExperiments : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetExperiments OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetExperiments OnError; - - /** - * Post one or more events. - * - * @param Names The ids of the experiments to query. Leave empty or pass an empty array to get them all. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Experiments", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Names")) - static USatoriClientGetExperiments* GetExperiments(USatoriClient* Client, USatoriSession* Session, const TArray& Names); - - virtual void Activate() override; - -private: - TArray Names; -}; - - -/** - * Get flags - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientGetFlags : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetFlags OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetFlags OnError; - - /** - * Post one or more events. - * - * @param Names The ids of the flags to query. Leave empty or pass an empty array to get them all. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Flags", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Names")) - static USatoriClientGetFlags* GetFlags(USatoriClient* Client, USatoriSession* Session, const TArray& Names); - - virtual void Activate() override; - -private: - TArray Names; -}; - - -/** - * Get flags - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientGetFlagOverrides : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetFlagOverrides OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetFlagOverrides OnError; - - /** - * Post one or more events. - * - * @param Names The ids of the flags to query overrides for. Leave empty or pass an empty array to get them all. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Flags", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "Names")) - static USatoriClientGetFlagOverrides* GetFlagOverrides(USatoriClient* Client, USatoriSession* Session, const TArray& Names); - - virtual void Activate() override; - -private: - TArray Names; -}; - - -/** - * Get live events - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientGetLiveEvents : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetLiveEvents OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetLiveEvents OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|LiveEvents", meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "LiveEventNames")) - static USatoriClientGetLiveEvents* GetLiveEvents(USatoriClient* Client, USatoriSession* Session, const TArray& LiveEventNames); - - virtual void Activate() override; - -private: - TArray LiveEventNames; -}; - - -/** - * Get messages - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientGetMessages : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetMessages OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriGetMessages OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Messages", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientGetMessages* GetMessages( - USatoriClient* Client, - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor); - - virtual void Activate() override; - -private: - int32 Limit; - bool Forward; - FString Cursor; -}; - - -/** - * Update message - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientUpdateMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriUpdateMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriUpdateMessage OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Messages", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientUpdateMessage* UpdateMessage( - USatoriClient* Client, - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime); - - virtual void Activate() override; - -private: - FString MessageId; - FDateTime ReadTime; - FDateTime ConsumeTime; -}; - - -/** - * Delete message - */ - -UCLASS() -class SATORIBLUEPRINTS_API USatoriClientDeleteMessage : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - - UPROPERTY() - TObjectPtr SatoriClient; - - UPROPERTY() - TObjectPtr UserSession; - - UPROPERTY(BlueprintAssignable) - FOnSatoriDeleteMessage OnSuccess; - - UPROPERTY(BlueprintAssignable) - FOnSatoriDeleteMessage OnError; - - /** - * Post one or more events. - * - * @param Events The ids of the users to add or invite as friends. - * @param Session The session of the user. - * @param Client The Client to use. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Messages", meta = (BlueprintInternalUseOnly = "true")) - static USatoriClientDeleteMessage* DeleteMessage(USatoriClient* Client, USatoriSession* Session, const FString& MessageId); - - virtual void Activate() override; - -private: - FString MessageId; -}; diff --git a/Satori/Source/SatoriBlueprints/SatoriBlueprints.Build.cs b/Satori/Source/SatoriBlueprints/SatoriBlueprints.Build.cs index 81f22b97f..0d5f0ef0b 100644 --- a/Satori/Source/SatoriBlueprints/SatoriBlueprints.Build.cs +++ b/Satori/Source/SatoriBlueprints/SatoriBlueprints.Build.cs @@ -40,7 +40,7 @@ public SatoriBlueprints(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", - "SatoriUnreal" + "SatoriApi" // ... add other public dependencies that you statically link with here ... } ); diff --git a/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.cpp b/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.cpp deleted file mode 100644 index 1e67a86ae..000000000 --- a/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "InternalLowLevelSatoriAPI.h" -#include "nakama-cpp/NError.h" -#include "nakama-cpp/log/NLogger.h" -#include "rapidjson/document.h" -#include "rapidjson/error/en.h" - -namespace Satori{ - bool jsonValueToStringVector(const rapidjson::Value& input, std::vector& output) { - for (rapidjson::Value::ConstMemberIterator iter = input.MemberBegin(); iter != input.MemberEnd(); ++iter){ - output.emplace_back(iter->value.GetString()); - } - return true; - } - - bool jsonValueToStringMap(const rapidjson::Value& input, std::unordered_map& output) { - for (rapidjson::Value::ConstMemberIterator iter = input.MemberBegin(); iter != input.MemberEnd(); ++iter){ - output[iter->name.GetString()] = iter->value.GetString(); - } - return true; - } - - bool SInternalAuthenticateLogoutRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalAuthenticateRefreshRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalAuthenticateRequest::fromJson(std::string jsonString) { - - return false; - } - - bool jsonValueToSEvent(const rapidjson::Value& input, SEvent& output){ - if(input.HasMember("name") && !input["name"].IsString()) { - return false; - } - output.name = input["name"].GetString(); - if(input.HasMember("id") && !input["id"].IsString()) { - return false; - } - output.id = input["id"].GetString(); - if(input.HasMember("metadata") && !jsonValueToStringMap(input["metadata"], output.metadata)) { - return false; - } - if(input.HasMember("value") && !input["value"].IsString()) { - return false; - } - output.value = input["value"].GetString(); - if(input.HasMember("timestamp") && !input["timestamp"].IsInt64()) { - return false; - } - output.timestamp = input["timestamp"].GetInt64(); - return true; - } - - bool SInternalEvent::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SEvent JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSEvent(d, *this); - } - - bool SInternalEventRequest::fromJson(std::string jsonString) { - - return false; - } - - bool jsonValueToSExperiment(const rapidjson::Value& input, SExperiment& output){ - if(input.HasMember("name") && !input["name"].IsString()) { - return false; - } - output.name = input["name"].GetString(); - if(input.HasMember("value") && !input["value"].IsString()) { - return false; - } - output.value = input["value"].GetString(); - return true; - } - - bool SInternalExperiment::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SExperiment JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSExperiment(d, *this); - } - - bool SInternalExperimentList::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SExperimentList JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - - if(d.HasMember("experiments")) { - if(!d["experiments"].IsArray()) { - return false; - } - for (auto& jsonExperiment : d["experiments"].GetArray()) { - SExperiment experiment; - if(!jsonValueToSExperiment(jsonExperiment, experiment)) { - return false; - } - this->experiments.emplace_back(experiment); - } - } - return true; - } - - bool jsonValueToSFlag(const rapidjson::Value& input, SFlag& output){ - if(input.HasMember("name") && !input["name"].IsString()) { - return false; - } - output.name = input["name"].GetString(); - if(input.HasMember("value") && !input["value"].IsString()) { - return false; - } - output.value = input["value"].GetString(); - // TODO: Figure out how to obtain this value and set it here if it can be obtained from the json we have - output.condition_changed = false; - return true; - } - - bool SInternalFlag::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SFlag JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSFlag(d, *this); - } - - bool SInternalFlagList::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SFlagList JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - - if(d.HasMember("flags")) { - if(!d["flags"].IsArray()) { - return false; - } - for (auto& jsonFlag : d["flags"].GetArray()) { - SFlag flag; - if(!jsonValueToSFlag(jsonFlag, flag)) { - return false; - } - this->flags.emplace_back(flag); - } - } - - return true; - } - - bool SInternalGetExperimentsRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalGetFlagsRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalGetLiveEventsRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalIdentifyRequest::fromJson(std::string jsonString) { - - return false; - } - - bool jsonValueToSLiveEvent(const rapidjson::Value& input, SLiveEvent& output){ - if(input.HasMember("name") && !input["name"].IsString()) { - return false; - } - output.name = input["name"].GetString(); - if(input.HasMember("description") && !input["description"].IsString()) { - return false; - } - output.description = input["description"].GetString(); - if(input.HasMember("value") && !input["value"].IsString()) { - return false; - } - output.value = input["value"].GetString(); - if(input.HasMember("active_start_time_sec") && !input["active_start_time_sec"].IsInt64()) { - return false; - } - output.active_start_time_sec = input["active_start_time_sec"].GetInt64(); - if(input.HasMember("active_end_time_sec") && !input["active_end_time_sec"].IsInt64()) { - return false; - } - output.active_end_time_sec = input["active_end_time_sec"].GetInt64(); - if(input.HasMember("id") && !input["id"].IsString()) { - return false; - } - output.id = input["id"].GetString(); - if(input.HasMember("start_time_sec") && !input["start_time_sec"].IsInt64()) { - return false; - } - output.start_time_sec = input["start_time_sec"].GetInt64(); - if(input.HasMember("end_time_sec") && !input["end_time_sec"].IsInt64()) { - return false; - } - output.end_time_sec = input["end_time_sec"].GetInt64(); - if(input.HasMember("duration_sec") && !input["duration_sec"].IsInt64()) { - return false; - } - output.duration_sec = input["duration_sec"].GetInt64(); - if(input.HasMember("reset_cron") && !input["reset_cron"].IsString()) { - return false; - } - output.reset_cron = input["reset_cron"].GetString(); - return true; - } - - bool SInternalLiveEvent::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SLiveEvent JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSLiveEvent(d, *this); - } - - bool SInternalLiveEventList::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SLiveEventList JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - - if(d.HasMember("live_events")) { - if(!d["live_events"].IsArray()) { - return false; - } - for (auto& jsonLiveEvent : d["live_events"].GetArray()) { - SLiveEvent liveEvent; - if(!jsonValueToSLiveEvent(jsonLiveEvent, liveEvent)) { - return false; - } - this->live_events.emplace_back(liveEvent); - } - } - return true; - } - - bool jsonValueToSProperties(const rapidjson::Value& input, SProperties& output){ - if(input.HasMember("default") && !jsonValueToStringMap(input["default"], output.default_properties)) { - return false; - } - if(input.HasMember("computed") && !jsonValueToStringMap(input["computed"], output.computed_properties)) { - return false; - } - if(input.HasMember("custom") && !jsonValueToStringMap(input["custom"], output.custom_properties)) { - return false; - } - return true; - } - - bool SInternalProperties::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SProperties JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSProperties(d, *this); - } - - bool SInternalSession::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SSession JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - // TODO: Error handling! Now if some field is not as expected, it just crashes. Example at: https://github.com/Tencent/rapidjson/blob/master/example/tutorial/tutorial.cpp - this->token = d["token"].GetString(); - this->refresh_token = d["refresh_token"].GetString(); - return jsonValueToSProperties(d["properties"], this->properties); - } - - bool SInternalUpdatePropertiesRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalGetMessageListRequest::fromJson(std::string jsonString) { - - return false; - } - - bool jsonValueToSMessage(const rapidjson::Value& input, SMessage& output){ - if(input.HasMember("schedule_id") && !input["schedule_id"].IsString()) { - return false; - } - output.schedule_id = input["schedule_id"].GetString(); - if(input.HasMember("send_time") && !input["send_time"].IsInt64()) { - return false; - } - output.send_time = input["send_time"].GetInt64(); - if(input.HasMember("metadata") && !jsonValueToStringMap(input["metadata"], output.metadata)) { - return false; - } - if(input.HasMember("create_time") && !input["create_time"].IsInt64()) { - return false; - } - output.create_time = input["create_time"].GetInt64(); - if(input.HasMember("update_time") && !input["update_time"].IsInt64()) { - return false; - } - output.update_time = input["update_time"].GetInt64(); - if(input.HasMember("read_time") && !input["read_time"].IsInt64()) { - return false; - } - output.read_time = input["read_time"].GetInt64(); - if(input.HasMember("consume_time") && !input["consume_time"].IsInt64()) { - return false; - } - output.consume_time = input["consume_time"].GetInt64(); - if(input.HasMember("text") && !input["text"].IsString()) { - return false; - } - output.text = input["text"].GetString(); - if(input.HasMember("id") && !input["id"].IsString()) { - return false; - } - output.id = input["id"].GetString(); - if(input.HasMember("title") && !input["title"].IsString()) { - return false; - } - output.title = input["title"].GetString(); - if(input.HasMember("image_url") && !input["image_url"].IsString()) { - return false; - } - output.image_url = input["image_url"].GetString(); - return true; - } - - bool SInternalMessage::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SMessage JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - return jsonValueToSMessage(d, *this); - } - - bool SInternalGetMessageListResponse::fromJson(std::string jsonString) { - rapidjson::Document d; - if(d.ParseInsitu(jsonString.data()).HasParseError()) { - NLOG_ERROR(Nakama::NError("Parse SGetMessageListResponse JSON failed. Error at " + std::to_string(d.GetErrorOffset()) + ": " + std::string(GetParseError_En(d.GetParseError())) + " HTTP body:<< " + jsonString + " >>.", Nakama::ErrorCode::InternalError)); - return false; - } - if(d.HasMember("messages")) { - if(!d["messages"].IsArray()) { - return false; - } - for (auto& jsonMessage : d["messages"].GetArray()) { - SMessage message; - if(!jsonValueToSMessage(jsonMessage, message)) { - return false; - } - this->messages.emplace_back(message); - } - } - if(d.HasMember("next_cursor") && !d["next_cursor"].IsString()) { - return false; - } - this->next_cursor = d["next_cursor"].GetString(); - if(d.HasMember("prev_cursor") && !d["prev_cursor"].IsString()) { - return false; - } - this->prev_cursor = d["prev_cursor"].GetString(); - if(d.HasMember("cacheable_cursor") && !d["cacheable_cursor"].IsString()) { - return false; - } - this->cacheable_cursor = d["cacheable_cursor"].GetString(); - return true; - } - - bool SInternalUpdateMessageRequest::fromJson(std::string jsonString) { - - return false; - } - - bool SInternalDeleteMessageRequest::fromJson(std::string jsonString) { - - return false; - } -} diff --git a/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.h b/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.h deleted file mode 100644 index 37295795e..000000000 --- a/Satori/Source/SatoriCore/Private/InternalLowLevelSatoriAPI.h +++ /dev/null @@ -1,118 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "nakama-cpp/satori/HardcodedLowLevelSatoriAPI.h" - -namespace Satori { - struct SFromJsonInterface { - virtual ~SFromJsonInterface() {} - virtual bool fromJson(std::string jsonString) = 0; - }; - - struct SInternalAuthenticateLogoutRequest : public SAuthenticateLogoutRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalAuthenticateRefreshRequest : public SAuthenticateRefreshRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalAuthenticateRequest : public SAuthenticateRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalEvent : public SEvent, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalEventRequest : public SEventRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalExperiment : public SExperiment, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalExperimentList : public SExperimentList, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalFlag : public SFlag, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalFlagList : public SFlagList, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalGetExperimentsRequest : public SGetExperimentsRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalGetFlagsRequest : public SGetFlagsRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalGetLiveEventsRequest : public SGetLiveEventsRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalIdentifyRequest : public SIdentifyRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalLiveEvent : public SLiveEvent, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalLiveEventList : public SLiveEventList, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalProperties : public SProperties, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalSession : public SSession, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalUpdatePropertiesRequest : public SUpdatePropertiesRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalGetMessageListRequest : public SGetMessageListRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalMessage : public SMessage, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalGetMessageListResponse : public SGetMessageListResponse, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalUpdateMessageRequest : public SUpdateMessageRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; - - struct SInternalDeleteMessageRequest : public SDeleteMessageRequest, public SFromJsonInterface { - bool fromJson(std::string jsonString) override; - }; -} \ No newline at end of file diff --git a/Satori/Source/SatoriCore/Private/SatoriBaseClient.cpp b/Satori/Source/SatoriCore/Private/SatoriBaseClient.cpp deleted file mode 100644 index e1f2a4722..000000000 --- a/Satori/Source/SatoriCore/Private/SatoriBaseClient.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriBaseClient.h" -#include "nakama-cpp/NException.h" - -namespace Satori { - std::future SatoriBaseClient::authenticateAsync( - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties - ) { - auto promise = std::make_shared>(); - - authenticate(id, defaultProperties, customProperties, - [=](const SSessionPtr& session) { - promise->set_value(session); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::authenticateRefreshAsync( - SSessionPtr inSession - ) { - auto promise = std::make_shared>(); - - authenticateRefresh(inSession, - [=](const SSessionPtr& outSession) { - promise->set_value(outSession); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::authenticateLogoutAsync( - SSessionPtr session - ) { - std::shared_ptr> promise = std::make_shared>(); - - authenticateLogout(session, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::deleteIdentityAsync( - SSessionPtr session - ) { - std::shared_ptr> promise = std::make_shared>(); - - deleteIdentity(session, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::postEventAsync( - SSessionPtr session, - const std::vector &events - ) { - std::shared_ptr> promise = std::make_shared>(); - - postEvent(session, events, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::getExperimentsAsync( - SSessionPtr session, - const std::vector &names - ) { - std::shared_ptr> promise = std::make_shared>(); - - getExperiments(session, names, - [=](const SExperimentList& experiments) { - promise->set_value(experiments); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::getFlagsAsync( - SSessionPtr session, - const std::vector& names - ) { - std::shared_ptr> promise = std::make_shared>(); - - getFlags(session, names, - [=](const SFlagList& flags) { - promise->set_value(flags); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::getLiveEventsAsync( - SSessionPtr session, - const std::vector& liveEventNames - ) { - std::shared_ptr> promise = std::make_shared>(); - - getLiveEvents(session, liveEventNames, - [=](const SLiveEventList& liveEvents) { - promise->set_value(liveEvents); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::identifyAsync( - SSessionPtr inSession, - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties - ) { - std::shared_ptr> promise = std::make_shared>(); - - identify(inSession, id, defaultProperties, customProperties, - [=](const SSessionPtr& outSession) { - promise->set_value(outSession); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::listIdentityPropertiesAsync( - SSessionPtr session - ) { - std::shared_ptr> promise = std::make_shared>(); - - listIdentityProperties(session, - [=](const SProperties& properties) { - promise->set_value(properties); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::updatePropertiesAsync( - SSessionPtr session, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - const bool recompute - ) { - std::shared_ptr> promise = std::make_shared>(); - - updateProperties(session, defaultProperties, customProperties, recompute, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::getMessagesAsync( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string &cursor - ) { - std::shared_ptr> promise = std::make_shared>(); - - getMessages(session, limit, forward, cursor, - [=](const SGetMessageListResponse& messages) { - promise->set_value(messages); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::updateMessageAsync( - SSessionPtr session, - const std::string &messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime - ) { - std::shared_ptr> promise = std::make_shared>(); - - updateMessage(session, messageId, readTime, consumeTime, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } - - std::future SatoriBaseClient::deleteMessageAsync( - SSessionPtr session, - const std::string &messageId - ) { - std::shared_ptr> promise = std::make_shared>(); - - deleteMessage(session, messageId, - [=]() { - promise->set_value(); - }, - [=](const Nakama::NError& error) { - promise->set_exception(std::make_exception_ptr(error)); - }); - - return promise->get_future(); - } -} diff --git a/Satori/Source/SatoriCore/Private/SatoriBaseClient.h b/Satori/Source/SatoriCore/Private/SatoriBaseClient.h deleted file mode 100644 index 9744112ed..000000000 --- a/Satori/Source/SatoriCore/Private/SatoriBaseClient.h +++ /dev/null @@ -1,112 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include "nakama-cpp/satori/SClientInterface.h" -#include "nakama-cpp/satori/SatoriClientFactory.h" -#include "nakama-cpp/satori/HardcodedLowLevelSatoriAPI.h" - -namespace Satori { - class SatoriBaseClient : public SClientInterface { - public: - std::future authenticateAsync( - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties - ) override; - - std::future authenticateRefreshAsync( - SSessionPtr session - ) override; - - std::future authenticateLogoutAsync( - SSessionPtr session - ) override; - - std::future deleteIdentityAsync( - SSessionPtr session - ) override; - - std::future postEventAsync( - SSessionPtr session, - const std::vector& events - ) override; - - std::future getExperimentsAsync( - SSessionPtr session, - const std::vector& names - ) override; - - std::future getFlagsAsync( - SSessionPtr session, - const std::vector& names - ) override; - - std::future getLiveEventsAsync( - SSessionPtr session, - const std::vector& liveEventNames - ) override; - - std::future identifyAsync( - SSessionPtr session, - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties - ) override; - - std::future listIdentityPropertiesAsync( - SSessionPtr session - ) override; - - std::future updatePropertiesAsync( - SSessionPtr session, - const std::unordered_map &defaultProperties, - const std::unordered_map &customProperties, - const bool recompute - ) override; - - std::future getMessagesAsync( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string& cursor - ) override; - - std::future updateMessageAsync( - SSessionPtr session, - const std::string& messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime - ) override; - - std::future deleteMessageAsync( - SSessionPtr session, - const std::string& messageId - ) override; - - protected: - int _port = 0; - std::string _host; - bool _ssl = false; - std::string _basicAuthMetadata; - Nakama::ErrorCallback _defaultErrorCallback; - void* _userData = nullptr; - Nakama::NPlatformParameters _platformParams; - }; -} diff --git a/Satori/Source/SatoriCore/Private/SatoriRestClient.cpp b/Satori/Source/SatoriCore/Private/SatoriRestClient.cpp deleted file mode 100644 index 5157c5942..000000000 --- a/Satori/Source/SatoriCore/Private/SatoriRestClient.cpp +++ /dev/null @@ -1,721 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "SatoriRestClient.h" -#include "DataHelper.h" -#include "nakama-cpp/NakamaVersion.h" -#include "StrUtil.h" -#include "RapidjsonHelper.h" - -#include -#include -#include - - -#include "DefaultSession.h" - -namespace Satori { - - void AddBoolArg(Nakama::NHttpQueryArgs& args, std::string&& name, bool value) { - value ? args.emplace(name, "true") : args.emplace(name, "false"); - } - - std::string jsonDocToStr(Nakama::rapidjson::Document& document) { - Nakama::rapidjson::StringBuffer buffer; - Nakama::rapidjson::Writer writer(buffer); - document.Accept(writer); - return buffer.GetString(); - } - - void addVarsToJsonDoc(Nakama::rapidjson::Document& document, const Nakama::NStringMap& vars) { - if (!vars.empty()) { - Nakama::rapidjson::Value jsonObj; - jsonObj.SetObject(); - - for (auto& p : vars) { - jsonObj.AddMember(Nakama::rapidjson::Value::StringRefType(p.first.c_str()), p.second, document.GetAllocator()); - } - - document.AddMember("vars", std::move(jsonObj), document.GetAllocator()); - } - } - - SatoriRestClient::SatoriRestClient( - const Nakama::NClientParameters& parameters, - Nakama::NHttpTransportPtr httpClient - ) : _httpClient(std::move(httpClient)) - { - NLOG(Nakama::NLogLevel::Info, "Created Satori Client. NakamaSdkVersion: %s", Nakama::getNakamaSdkVersion()); - - _host = parameters.host; - _ssl = parameters.ssl; - _platformParams = parameters.platformParams; - _port = parameters.port; - std::string baseUrl; - - - if (_port == Nakama::DEFAULT_PORT) { - _port = parameters.ssl ? 443 : 7450; - NLOG(Nakama::NLogLevel::Info, "using default port %d", _port); - } - - _ssl ? baseUrl.append("https") : baseUrl.append("http"); - baseUrl.append("://").append(parameters.host).append(":").append(std::to_string(_port)); - - _httpClient->setBaseUri(baseUrl); - - _basicAuthMetadata = "Basic " + Nakama::base64Encode(parameters.serverKey + ":"); - } - - SatoriRestClient::~SatoriRestClient() { - SatoriRestClient::disconnect(); - - if (_reqContexts.size() > 0) - { - NLOG(Nakama::NLogLevel::Warn, "Not handled %u request(s) detected.", _reqContexts.size()); - - for (RestReqContext* reqContext : _reqContexts) - { - delete reqContext; - } - - _reqContexts.clear(); - } - } - - void SatoriRestClient::disconnect() { - _httpClient->cancelAllRequests(); - } - - void SatoriRestClient::tick() { - _httpClient->tick(); - } - - void SatoriRestClient::authenticate( - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& computedProperties, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - std::shared_ptr sessionData(std::make_shared()); - RestReqContext* ctx = createReqContext(sessionData); - setBasicAuth(ctx); - ctx->successCallback = [sessionData, successCallback]() - { - successCallback(sessionData); - }; - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - document.AddMember("id", id, document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::POST, "/v1/authenticate", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::authenticateRefresh( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - std::shared_ptr sessionData(std::make_shared()); - RestReqContext* ctx = createReqContext(sessionData); - setBasicAuth(ctx); - ctx->successCallback = [sessionData, successCallback]() - { - successCallback(sessionData); - }; - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - document.AddMember("refresh_token", session->refresh_token, document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::POST, "/v1/authenticate/refresh", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::authenticateLogout( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - document.AddMember("token", session->token, document.GetAllocator()); - document.AddMember("refresh_token", session->refresh_token, document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::POST, "/v1/authenticate/logout", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::deleteIdentity( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::DEL, "/v1/identity", ""); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::postEvent( - SSessionPtr session, - const std::vector& events, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - Nakama::rapidjson::Value jsonEvents; - jsonEvents.SetArray(); - for (const SEvent& event: events) - { - Nakama::rapidjson::Value jsonEvent; - jsonEvent.SetObject(); - - jsonEvent.AddMember("name", event.name, document.GetAllocator()); - jsonEvent.AddMember("id", event.id, document.GetAllocator()); - jsonEvent.AddMember("value", event.value, document.GetAllocator()); - google::protobuf::Timestamp timeProto = google::protobuf::util::TimeUtil::MillisecondsToTimestamp( - static_cast(event.timestamp)); - std::string timeString = google::protobuf::util::TimeUtil::ToString(timeProto); - jsonEvent.AddMember("timestamp", std::move(timeString), document.GetAllocator()); - Nakama::rapidjson::Value jsonMetadata; - jsonMetadata.SetObject(); - for (auto& p : event.metadata) { - jsonMetadata.AddMember(Nakama::rapidjson::Value::StringRefType(p.first.c_str()), p.second, document.GetAllocator()); - } - jsonEvent.AddMember("metadata", std::move(jsonMetadata), document.GetAllocator()); - - jsonEvents.PushBack(std::move(jsonEvent), document.GetAllocator()); - } - document.AddMember("events", std::move(jsonEvents), document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::POST, "/v1/event", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::getExperiments( - SSessionPtr session, - const std::vector& names, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - for (auto& name : names) { - args.emplace("names", name); - } - - std::shared_ptr experimentsData(std::make_shared()); - RestReqContext* ctx(createReqContext(experimentsData)); - setSessionAuth(ctx, session); - ctx->successCallback = [experimentsData, successCallback]() - { - successCallback(static_cast(*experimentsData)); - }; - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::GET, "/v1/experiment", "", std::move(args)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::getFlags( - SSessionPtr session, - const std::vector& names, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - for (auto& name : names) { - args.emplace("names", name); - } - - std::shared_ptr flagsData(std::make_shared()); - RestReqContext* ctx(createReqContext(flagsData)); - setSessionAuth(ctx, session); - ctx->successCallback = [flagsData, successCallback]() - { - successCallback(static_cast(*flagsData)); - }; - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::GET, "/v1/flag", "", std::move(args)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::getLiveEvents( - SSessionPtr session, - const std::vector& liveEventNames, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - for (auto& liveEventName : liveEventNames) { - args.emplace("names", liveEventName); - } - - std::shared_ptr liveEventsData(std::make_shared()); - RestReqContext* ctx(createReqContext(liveEventsData)); - setSessionAuth(ctx, session); - ctx->successCallback = [liveEventsData, successCallback]() - { - successCallback(static_cast(*liveEventsData)); - }; - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::GET, "/v1/live-event", "", std::move(args)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::identify( - SSessionPtr session, - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - std::shared_ptr sessionData(std::make_shared()); - RestReqContext* ctx = createReqContext(sessionData); - setSessionAuth(ctx, session); - ctx->successCallback = [sessionData, successCallback]() - { - successCallback(sessionData); - }; - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - document.AddMember("id", id, document.GetAllocator()); - - Nakama::rapidjson::Value jsonDefaultProperties; - jsonDefaultProperties.SetObject(); - for (auto& obj : defaultProperties) - { - jsonDefaultProperties.AddMember( - Nakama::rapidjson::Value(obj.first,document.GetAllocator()).Move(), - Nakama::rapidjson::Value(obj.second, document.GetAllocator()).Move(), - document.GetAllocator() - ); - } - document.AddMember("default", std::move(jsonDefaultProperties), document.GetAllocator()); - - Nakama::rapidjson::Value jsonCustomProperties; - jsonCustomProperties.SetObject(); - for (auto& obj : customProperties) - { - jsonCustomProperties.AddMember( - Nakama::rapidjson::Value(obj.first,document.GetAllocator()).Move(), - Nakama::rapidjson::Value(obj.second, document.GetAllocator()).Move(), - document.GetAllocator() - ); - } - document.AddMember("custom", std::move(jsonCustomProperties), document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::PUT, "/v1/identify", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::listIdentityProperties( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - std::shared_ptr propertiesData(std::make_shared()); - RestReqContext* ctx(createReqContext(propertiesData)); - setSessionAuth(ctx, session); - ctx->successCallback = [propertiesData, successCallback]() - { - successCallback(static_cast(*propertiesData)); - }; - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::GET, "/v1/properties", "", std::move(args)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::updateProperties( - SSessionPtr session, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - const bool recompute, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - Nakama::rapidjson::Value jsonDefaultProperties; - jsonDefaultProperties.SetObject(); - for (auto& obj : defaultProperties) - { - jsonDefaultProperties.AddMember( - Nakama::rapidjson::Value(obj.first,document.GetAllocator()).Move(), - Nakama::rapidjson::Value(obj.second, document.GetAllocator()).Move(), - document.GetAllocator() - ); - } - document.AddMember("default", std::move(jsonDefaultProperties), document.GetAllocator()); - - Nakama::rapidjson::Value jsonCustomProperties; - jsonCustomProperties.SetObject(); - for (auto& obj : customProperties) - { - jsonCustomProperties.AddMember( - Nakama::rapidjson::Value(obj.first,document.GetAllocator()).Move(), - Nakama::rapidjson::Value(obj.second, document.GetAllocator()).Move(), - document.GetAllocator() - ); - } - document.AddMember("custom", std::move(jsonCustomProperties), document.GetAllocator()); - document.AddMember("recompute", recompute, document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::PUT, "/v1/properties", std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::getMessages( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string& cursor, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - args.emplace("limit", std::to_string(limit)); - args.emplace("forward", forward? "true" : "false"); - if (!cursor.empty()) {args.emplace("cursor", Nakama::encodeURIComponent(cursor));} - - std::shared_ptr messagesData(std::make_shared()); - RestReqContext* ctx(createReqContext(messagesData)); - setSessionAuth(ctx, session); - ctx->successCallback = [messagesData, successCallback]() - { - successCallback(static_cast(*messagesData)); - }; - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::GET, "/v1/message", "", std::move(args)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::updateMessage( - SSessionPtr session, - const std::string& messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - Nakama::rapidjson::Document document; - document.SetObject(); - - document.AddMember("read_time", readTime, document.GetAllocator()); - document.AddMember("consume_time", consumeTime, document.GetAllocator()); - - std::string body = jsonDocToStr(document); - - sendReq(ctx, Nakama::NHttpReqMethod::PUT, "/v1/message/" + Nakama::encodeURIComponent(messageId), std::move(body)); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - void SatoriRestClient::deleteMessage( - SSessionPtr session, - const std::string& messageId, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) { - try { - NLOG_INFO("..."); - - Nakama::NHttpQueryArgs args; - - RestReqContext* ctx = createReqContext(nullptr); - setSessionAuth(ctx, session); - ctx->successCallback = std::move(successCallback); - ctx->errorCallback = std::move(errorCallback); - - sendReq(ctx, Nakama::NHttpReqMethod::DEL, "/v1/message/" + Nakama::encodeURIComponent(messageId), ""); - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - } - - RestReqContext* SatoriRestClient::createReqContext(std::shared_ptr data) { - RestReqContext* ctx = new RestReqContext(); - ctx->data = data; - _reqContexts.emplace(ctx); - return ctx; - } - - void SatoriRestClient::setBasicAuth(RestReqContext* ctx) { - ctx->auth.append(_basicAuthMetadata); - } - - void SatoriRestClient::setSessionAuth(RestReqContext* ctx, const SSessionPtr session) { - ctx->auth.append("Bearer ").append(session->token); - } - - void SatoriRestClient::sendReq( - RestReqContext* ctx, - Nakama::NHttpReqMethod method, - std::string&& path, - std::string&& body, - Nakama::NHttpQueryArgs &&args - ) { - if(ctx == nullptr) { - reqError(nullptr, Nakama::NError("Satori request context not found.", Nakama::ErrorCode::InternalError)); - return; - } - Nakama::NHttpRequest req; - - req.method = method; - req.path = std::move(path); - req.body = std::move(body); - req.queryArgs = std::move(args); - - req.headers.emplace("Accept", "application/json"); - req.headers.emplace("Content-Type", "application/json"); - if (!ctx->auth.empty()) { - req.headers.emplace("Authorization", std::move(ctx->auth)); - } - - _httpClient->request(req, [this, ctx](Nakama::NHttpResponsePtr response) { - // TODO: Convert this boilerplate lambda back into a function that can be used from within Satori cpp. Boilerplate begins here ============ - [&]()//void RestClient::onResponse(RestReqContext* reqContext, NHttpResponsePtr response) - { - auto it = _reqContexts.find(ctx); - if (it != _reqContexts.end()) { - if (response->statusCode == 200) {// OK - if (ctx && ctx->successCallback) { - bool ok = true; - if (ctx->data) { - ok = ctx->data->fromJson(response->body); - if (!ok) { - reqError(ctx, Nakama::NError("Parse JSON failed for Satori. HTTP body: <<" + response->body + ">>", Nakama::ErrorCode::InternalError)); - } - } - - if (ok) { - ctx->successCallback(); - } - } - } else { - std::string errMessage; - Nakama::ErrorCode code = Nakama::ErrorCode::Unknown; - - if (response->statusCode == Nakama::InternalStatusCodes::CONNECTION_ERROR) { - code = Nakama::ErrorCode::ConnectionError; - errMessage.append("message: ").append(response->errorMessage); - } else if (response->statusCode == Nakama::InternalStatusCodes::CANCELLED_BY_USER) { - code = Nakama::ErrorCode::CancelledByUser; - errMessage.append("message: ").append(response->errorMessage); - } else if (response->statusCode == Nakama::InternalStatusCodes::INTERNAL_TRANSPORT_ERROR) { - code = Nakama::ErrorCode::InternalError; - errMessage.append("message: ").append(response->errorMessage); - } else if (!response->body.empty() && response->body[0] == '{') {// have to be JSON - /* - try { - rapidjson::Document document; - - if (document.Parse(response->body).HasParseError()) { - errMessage = "Parse JSON failed: " + response->body; - code = Nakama::ErrorCode::InternalError; - } else { - auto& jsonMessage = document["message"]; - auto& jsonCode = document["code"]; - - if (jsonMessage.IsString()) { - errMessage.append("message: ").append(jsonMessage.GetString()); - } - - if (jsonCode.IsNumber()) { - int serverErrCode = jsonCode.GetInt(); - - switch (serverErrCode) { - case grpc::StatusCode::UNAVAILABLE : code = ErrorCode::ConnectionError; break; - case grpc::StatusCode::INTERNAL : code = ErrorCode::InternalError; break; - case grpc::StatusCode::NOT_FOUND : code = ErrorCode::NotFound; break; - case grpc::StatusCode::ALREADY_EXISTS : code = ErrorCode::AlreadyExists; break; - case grpc::StatusCode::INVALID_ARGUMENT : code = ErrorCode::InvalidArgument; break; - case grpc::StatusCode::UNAUTHENTICATED : code = ErrorCode::Unauthenticated; break; - case grpc::StatusCode::PERMISSION_DENIED: code = ErrorCode::PermissionDenied; break; - - default: - errMessage.append("\ncode: ").append(std::to_string(serverErrCode)); - break; - } - } - } - } catch (std::exception& e) { - NLOG_ERROR("exception: " + std::string(e.what())); - } - */ - } - - if (errMessage.empty()) { - errMessage.append("message: ").append(response->errorMessage); - errMessage.append("\nHTTP status: ").append(std::to_string(response->statusCode)); - errMessage.append("\nbody: ").append(response->body); - } - - reqError(ctx, Nakama::NError(std::move(errMessage), code)); - } - - delete ctx; - _reqContexts.erase(it); - } else { - reqError(nullptr, Nakama::NError("Not found satori request context.", Nakama::ErrorCode::InternalError)); - delete ctx; - } - }(); - // TODO: Convert this boilerplate lambda back into a function that can be used from within Satori cpp. Boilerplate ends here ============ - }); - } - - void SatoriRestClient::reqError(RestReqContext* ctx, const Nakama::NError& error) const { - NLOG_ERROR(error); - - if (ctx && ctx->errorCallback) { - ctx->errorCallback(error); - } else if (_defaultErrorCallback) { - _defaultErrorCallback(error); - } else { - NLOG_WARN("^ error not handled"); - } - } -} diff --git a/Satori/Source/SatoriCore/Private/SatoriRestClient.h b/Satori/Source/SatoriCore/Private/SatoriRestClient.h deleted file mode 100644 index df8db9fa7..000000000 --- a/Satori/Source/SatoriCore/Private/SatoriRestClient.h +++ /dev/null @@ -1,163 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include "SatoriBaseClient.h" -#include "InternalLowLevelSatoriAPI.h" -#include "nakama-cpp/log/NLogger.h" - -namespace Satori { - struct RestReqContext - { - std::string auth; - std::function successCallback; - Nakama::ErrorCallback errorCallback; - std::shared_ptr data = nullptr; - }; - - class SatoriRestClient : public SatoriBaseClient { - public: - explicit SatoriRestClient(const Nakama::NClientParameters& parameters, Nakama::NHttpTransportPtr httpClient); - ~SatoriRestClient() override; - void disconnect() override; - void tick() override; - - void authenticate( - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void authenticateRefresh( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void authenticateLogout( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void deleteIdentity( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void postEvent( - SSessionPtr session, - const std::vector& events, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void getExperiments( - SSessionPtr session, - const std::vector& names, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void getFlags( - SSessionPtr session, - const std::vector& names, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void getLiveEvents( - SSessionPtr session, - const std::vector& liveEventNames, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void identify( - SSessionPtr session, - const std::string& id, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void listIdentityProperties( - SSessionPtr session, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void updateProperties( - SSessionPtr session, - const std::unordered_map& defaultProperties, - const std::unordered_map& customProperties, - const bool recompute, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void getMessages( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string& cursor, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void updateMessage( - SSessionPtr session, - const std::string& messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - - void deleteMessage( - SSessionPtr session, - const std::string& messageId, - std::function successCallback, - Nakama::ErrorCallback errorCallback - ) override; - private: - RestReqContext* createReqContext(std::shared_ptr data); - void setBasicAuth(RestReqContext* ctx); - void setSessionAuth(RestReqContext* ctx, const SSessionPtr session); - // Takes ownership of ctx pointer - void sendReq( - RestReqContext* ctx, - Nakama::NHttpReqMethod method, - std::string&& path, - std::string&& body, - Nakama::NHttpQueryArgs&& args = Nakama::NHttpQueryArgs()); - - // Does not take ownership of ctx pointer - void reqError(RestReqContext* ctx, const Nakama::NError &error) const; - - private: - std::set _reqContexts; - Nakama::NHttpTransportPtr _httpClient; - }; -} - diff --git a/Satori/Source/SatoriCore/Public/satori-cpp/HardcodedLowLevelSatoriAPI.h b/Satori/Source/SatoriCore/Public/satori-cpp/HardcodedLowLevelSatoriAPI.h deleted file mode 100644 index 63d233835..000000000 --- a/Satori/Source/SatoriCore/Public/satori-cpp/HardcodedLowLevelSatoriAPI.h +++ /dev/null @@ -1,258 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include "nakama-cpp/NTypes.h" - -namespace Satori { - - // Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. - struct SAuthenticateLogoutRequest { - // Session token to log out. - std::string token; - // Refresh token to invalidate. - std::string refresh_token; - }; - - // Authenticate against the server with a refresh token. - struct SAuthenticateRefreshRequest { - // Refresh token. - std::string refresh_token; - }; - - // Authentication request - struct SAuthenticateRequest { - // Identity ID. Must be between eight and 128 characters (inclusive). - // Must be an alphanumeric string with only underscores and hyphens allowed. - std::string id; - // Optional default properties to update with this call. - // If not set, properties are left as they are on the server. - std::unordered_map default_properties; - // Optional custom properties to update with this call. - // If not set, properties are left as they are on the server. - std::unordered_map custom_properties; - }; - - // A single event. Usually, but not necessarily, part of a batch. - struct SEvent { - // Event name. - std::string name; - // Optional event ID assigned by the client, used to de-duplicate in retransmission scenarios. - // If not supplied the server will assign a randomly generated unique event identifier. - std::string id; - // Event metadata, if any. - std::unordered_map metadata; - // Optional value. - std::string value; - // The time when the event was triggered on the producer side. Unit is unix time milliseconds - Nakama::NTimestamp timestamp; - }; - - // Publish an event to the server - struct SEventRequest { - // Some number of events produced by a client. - std::vector events; - }; - - // An experiment that this user is partaking. - struct SExperiment { - // Experiment name - std::string name; - // Value associated with this Experiment. - std::string value; - }; - - // All experiments that this identity is involved with. - struct SExperimentList { - // All experiments for this identity. - std::vector experiments; - }; - - // Feature flag available to the identity. - struct SFlag { - // Flag name - std::string name; - // Value associated with this flag. - std::string value; - // Whether the value for this flag has conditionally changed from the default state. - bool condition_changed; - }; - - // All flags available to the identity - struct SFlagList { - // All flags - std::vector flags; - }; - - // Request to get all experiments data. - struct SGetExperimentsRequest { - // Experiment names; if empty string all experiments are returned. - std::vector names; - }; - - // Request to get all flags data. - struct SGetFlagsRequest { - // Flag names; if empty string all flags are returned. - std::vector names; - }; - - // Request to get all live events. - struct SGetLiveEventsRequest { - // Live event names; if empty string all live events are returned. - std::vector names; - }; - - // Enrich/replace the current session with a new ID. - struct SIdentifyRequest { - // Identity ID to enrich the current session and return a new session. Old session will no longer be usable. - std::string id; - // Optional default properties to update with this call. - // If not set, properties are left as they are on the server. - std::unordered_map default_properties; - // Optional custom properties to update with this call. - // If not set, properties are left as they are on the server. - std::unordered_map custom_properties; - }; - - // A single live event. - struct SLiveEvent { - // Name. - std::string name; - // Description. - std::string description; - // Event value. - std::string value; - // Start time of current event run. - int64_t active_start_time_sec; - // End time of current event run. - int64_t active_end_time_sec; - // The live event identifier. - std::string id; - // Start time. - int64_t start_time_sec; - // End time, 0 if it repeats forever. - int64_t end_time_sec; - // Duration in seconds. - int64_t duration_sec; - // Reset CRON schedule, if configured. - std::string reset_cron; - }; - - // List of Live events. - struct SLiveEventList { - // Live events. - std::vector live_events; - }; - - // Properties associated with an identity. - struct SProperties { - // Event default properties. - std::unordered_map default_properties; - // Event computed properties. - std::unordered_map computed_properties; - // Event custom properties. - std::unordered_map custom_properties; - }; - - // A session. - struct SSession { - // Token credential. - std::string token; - // Refresh token. - std::string refresh_token; - // Properties associated with this identity. - SProperties properties; - }; - - // Update Properties associated with this identity. - struct SUpdatePropertiesRequest { - // Event default properties. - std::unordered_map default_properties; - // Event custom properties. - std::unordered_map custom_properties; - // Informs the server to recompute the audience membership of the identity. - bool recompute; - }; - - - struct SGetMessageListRequest { - // Max number of messages to return. Between 1 and 100. - int32_t limit; - // True if listing should be older messages to newer, false if reverse. - bool forward; - // A pagination cursor, if any. - std::string cursor; - }; - - // A scheduled message. - struct SMessage { - // The identifier of the schedule. - std::string schedule_id; - // The send time for the message. - Nakama::NTimestamp send_time; - // A key-value pairs of metadata. - std::unordered_map metadata; - // The time the message was created. - Nakama::NTimestamp create_time; - // The time the message was updated. - Nakama::NTimestamp update_time; - // The time the message was read by the client. - Nakama::NTimestamp read_time; - // The time the message was consumed by the identity. - Nakama::NTimestamp consume_time; - // The message's text. - std::string text; - // The message's unique identifier. - std::string id; - // The message's title. - std::string title; - // The message's image url. - std::string image_url; - }; - - // A response containing all the messages for an identity. - struct SGetMessageListResponse { - // The list of messages. - std::vector messages; - // The cursor to send when retrieving the next page, if any. - std::string next_cursor; - // The cursor to send when retrieving the previous page, if any. - std::string prev_cursor; - // Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. - std::string cacheable_cursor; - }; - - // The request to update the status of a message. - struct SUpdateMessageRequest { - // The identifier of the messages. - std::string id; - // The time the message was read at the client. - Nakama::NTimestamp read_time; - // The time the message was consumed by the identity. - Nakama::NTimestamp consume_time; - }; - - // The request to delete a scheduled message. - struct SDeleteMessageRequest { - // The identifier of the message. - std::string id; - }; -} diff --git a/Satori/Source/SatoriCore/Public/satori-cpp/SClientInterface.h b/Satori/Source/SatoriCore/Public/satori-cpp/SClientInterface.h deleted file mode 100644 index 73a5340c0..000000000 --- a/Satori/Source/SatoriCore/Public/satori-cpp/SClientInterface.h +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2024 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "HardcodedLowLevelSatoriAPI.h" -#include "nakama-cpp/NClientInterface.h" - -namespace Satori { - - using SSessionPtr = std::shared_ptr; - - /** - * A client interface to interact with Satori server. - */ - class NAKAMA_API SClientInterface - { - public: - virtual ~SClientInterface() {} - - virtual void disconnect() = 0; - virtual void tick() = 0; - - virtual void authenticate( - const std::string& id, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future authenticateAsync( - const std::string& id, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}) = 0; - - virtual void authenticateRefresh( - SSessionPtr session, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future authenticateRefreshAsync( - SSessionPtr session) = 0; - - virtual void authenticateLogout( - SSessionPtr session, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future authenticateLogoutAsync( - SSessionPtr session) = 0; - - virtual void deleteIdentity( - SSessionPtr session, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future deleteIdentityAsync( - SSessionPtr session) = 0; - - virtual void postEvent( - SSessionPtr session, - const std::vector& events, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future postEventAsync( - SSessionPtr session, - const std::vector& events) = 0; - - virtual void getExperiments( - SSessionPtr session, - const std::vector& names, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future getExperimentsAsync( - SSessionPtr session, - const std::vector& names) = 0; - - virtual void getFlags( - SSessionPtr session, - const std::vector& names = {}, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future getFlagsAsync( - SSessionPtr session, - const std::vector& names = {}) = 0; - - /** - * Request to get all live events. - * - * @param session The session of the user. - * @param liveEventNames Live event names; if empty string all live events are returned. - */ - virtual void getLiveEvents( - SSessionPtr session, - const std::vector& liveEventNames = {}, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - /** - * Fetch one or more users by id, usernames, and Facebook ids. - * - * @param session The session of the user. - * @param liveEventNames Live event names; if empty string all live events are returned. - */ - virtual std::future getLiveEventsAsync( - SSessionPtr session, - const std::vector& liveEventNames = {}) = 0; - - virtual void identify( - SSessionPtr session, - const std::string& id, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future identifyAsync( - SSessionPtr session, - const std::string& id, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}) = 0; - - virtual void listIdentityProperties( - SSessionPtr session, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future listIdentityPropertiesAsync( - SSessionPtr session) = 0; - - virtual void updateProperties( - SSessionPtr session, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}, - const bool recompute = false, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future updatePropertiesAsync( - SSessionPtr session, - const std::unordered_map& defaultProperties = {}, - const std::unordered_map& customProperties = {}, - const bool recompute = false) = 0; - - virtual void getMessages( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string& cursor, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future getMessagesAsync( - SSessionPtr session, - int32_t limit, - bool forward, - const std::string& cursor) = 0; - - virtual void updateMessage( - SSessionPtr session, - const std::string& messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future updateMessageAsync( - SSessionPtr session, - const std::string& messageId, - const Nakama::NTimestamp readTime, - const Nakama::NTimestamp consumeTime) = 0; - - virtual void deleteMessage( - SSessionPtr session, - const std::string& messageId, - std::function successCallback = nullptr, - Nakama::ErrorCallback errorCallback = nullptr) = 0; - - virtual std::future deleteMessageAsync( - SSessionPtr session, - const std::string& messageId) = 0; - }; - - using SClientPtr = std::shared_ptr; - -} diff --git a/Satori/Source/SatoriCore/Public/satori-cpp/SatoriClientFactory.h b/Satori/Source/SatoriCore/Public/satori-cpp/SatoriClientFactory.h deleted file mode 100644 index f197dd3cc..000000000 --- a/Satori/Source/SatoriCore/Public/satori-cpp/SatoriClientFactory.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -* Copyright 2024 The Nakama Authors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#pragma once - -#include "nakama-cpp/ClientFactory.h" -#include "SClientInterface.h" - -namespace Satori { - NAKAMA_API Satori::SClientPtr createRestClient(const Nakama::NClientParameters& parameters, Nakama::NHttpTransportPtr httpTransport = nullptr); -} \ No newline at end of file diff --git a/Satori/Source/SatoriCore/Public/satori-cpp/satori.pb.h b/Satori/Source/SatoriCore/Public/satori-cpp/satori.pb.h deleted file mode 100644 index f445d856f..000000000 --- a/Satori/Source/SatoriCore/Public/satori-cpp/satori.pb.h +++ /dev/null @@ -1,8753 +0,0 @@ -// Generated by the protocol buffer compiler. DO NOT EDIT! -// NO CHECKED-IN PROTOBUF GENCODE -// source: satori.proto -// Protobuf C++ Version: 5.28.2 - -#ifndef GOOGLE_PROTOBUF_INCLUDED_satori_2eproto_2epb_2eh -#define GOOGLE_PROTOBUF_INCLUDED_satori_2eproto_2epb_2eh - -#include -#include -#include -#include - -#include "google/protobuf/runtime_version.h" -#if PROTOBUF_VERSION != 5028002 -#error "Protobuf C++ gencode is built with an incompatible version of" -#error "Protobuf C++ headers/runtime. See" -#error "https://protobuf.dev/support/cross-version-runtime-guarantee/#cpp" -#endif -#include "google/protobuf/io/coded_stream.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/arenastring.h" -#include "google/protobuf/generated_message_tctable_decl.h" -#include "google/protobuf/generated_message_util.h" -#include "google/protobuf/metadata_lite.h" -#include "google/protobuf/generated_message_reflection.h" -#include "google/protobuf/message.h" -#include "google/protobuf/repeated_field.h" // IWYU pragma: export -#include "google/protobuf/extension_set.h" // IWYU pragma: export -#include "google/protobuf/map.h" // IWYU pragma: export -#include "google/protobuf/map_entry.h" -#include "google/protobuf/map_field_inl.h" -#include "google/protobuf/unknown_field_set.h" -#include "google/api/annotations.pb.h" -#include "google/protobuf/empty.pb.h" -#include "google/protobuf/timestamp.pb.h" -#include "protoc-gen-openapiv2/options/annotations.pb.h" -#include "google/protobuf/wrappers.pb.h" -// @@protoc_insertion_point(includes) - -// Must be included last. -#include "google/protobuf/port_def.inc" - -#define PROTOBUF_INTERNAL_EXPORT_satori_2eproto - -namespace google { -namespace protobuf { -namespace internal { -class AnyMetadata; -} // namespace internal -} // namespace protobuf -} // namespace google - -// Internal implementation detail -- do not use these members. -struct TableStruct_satori_2eproto { - static const ::uint32_t offsets[]; -}; -extern const ::google::protobuf::internal::DescriptorTable - descriptor_table_satori_2eproto; -namespace satori { -namespace api { -class AuthenticateLogoutRequest; -struct AuthenticateLogoutRequestDefaultTypeInternal; -extern AuthenticateLogoutRequestDefaultTypeInternal _AuthenticateLogoutRequest_default_instance_; -class AuthenticateRefreshRequest; -struct AuthenticateRefreshRequestDefaultTypeInternal; -extern AuthenticateRefreshRequestDefaultTypeInternal _AuthenticateRefreshRequest_default_instance_; -class AuthenticateRequest; -struct AuthenticateRequestDefaultTypeInternal; -extern AuthenticateRequestDefaultTypeInternal _AuthenticateRequest_default_instance_; -class AuthenticateRequest_CustomEntry_DoNotUse; -struct AuthenticateRequest_CustomEntry_DoNotUseDefaultTypeInternal; -extern AuthenticateRequest_CustomEntry_DoNotUseDefaultTypeInternal _AuthenticateRequest_CustomEntry_DoNotUse_default_instance_; -class AuthenticateRequest_DefaultEntry_DoNotUse; -struct AuthenticateRequest_DefaultEntry_DoNotUseDefaultTypeInternal; -extern AuthenticateRequest_DefaultEntry_DoNotUseDefaultTypeInternal _AuthenticateRequest_DefaultEntry_DoNotUse_default_instance_; -class DeleteMessageRequest; -struct DeleteMessageRequestDefaultTypeInternal; -extern DeleteMessageRequestDefaultTypeInternal _DeleteMessageRequest_default_instance_; -class Event; -struct EventDefaultTypeInternal; -extern EventDefaultTypeInternal _Event_default_instance_; -class EventRequest; -struct EventRequestDefaultTypeInternal; -extern EventRequestDefaultTypeInternal _EventRequest_default_instance_; -class Event_MetadataEntry_DoNotUse; -struct Event_MetadataEntry_DoNotUseDefaultTypeInternal; -extern Event_MetadataEntry_DoNotUseDefaultTypeInternal _Event_MetadataEntry_DoNotUse_default_instance_; -class Experiment; -struct ExperimentDefaultTypeInternal; -extern ExperimentDefaultTypeInternal _Experiment_default_instance_; -class ExperimentList; -struct ExperimentListDefaultTypeInternal; -extern ExperimentListDefaultTypeInternal _ExperimentList_default_instance_; -class Flag; -struct FlagDefaultTypeInternal; -extern FlagDefaultTypeInternal _Flag_default_instance_; -class FlagList; -struct FlagListDefaultTypeInternal; -extern FlagListDefaultTypeInternal _FlagList_default_instance_; -class GetExperimentsRequest; -struct GetExperimentsRequestDefaultTypeInternal; -extern GetExperimentsRequestDefaultTypeInternal _GetExperimentsRequest_default_instance_; -class GetFlagsRequest; -struct GetFlagsRequestDefaultTypeInternal; -extern GetFlagsRequestDefaultTypeInternal _GetFlagsRequest_default_instance_; -class GetLiveEventsRequest; -struct GetLiveEventsRequestDefaultTypeInternal; -extern GetLiveEventsRequestDefaultTypeInternal _GetLiveEventsRequest_default_instance_; -class GetMessageListRequest; -struct GetMessageListRequestDefaultTypeInternal; -extern GetMessageListRequestDefaultTypeInternal _GetMessageListRequest_default_instance_; -class GetMessageListResponse; -struct GetMessageListResponseDefaultTypeInternal; -extern GetMessageListResponseDefaultTypeInternal _GetMessageListResponse_default_instance_; -class IdentifyRequest; -struct IdentifyRequestDefaultTypeInternal; -extern IdentifyRequestDefaultTypeInternal _IdentifyRequest_default_instance_; -class IdentifyRequest_CustomEntry_DoNotUse; -struct IdentifyRequest_CustomEntry_DoNotUseDefaultTypeInternal; -extern IdentifyRequest_CustomEntry_DoNotUseDefaultTypeInternal _IdentifyRequest_CustomEntry_DoNotUse_default_instance_; -class IdentifyRequest_DefaultEntry_DoNotUse; -struct IdentifyRequest_DefaultEntry_DoNotUseDefaultTypeInternal; -extern IdentifyRequest_DefaultEntry_DoNotUseDefaultTypeInternal _IdentifyRequest_DefaultEntry_DoNotUse_default_instance_; -class LiveEvent; -struct LiveEventDefaultTypeInternal; -extern LiveEventDefaultTypeInternal _LiveEvent_default_instance_; -class LiveEventList; -struct LiveEventListDefaultTypeInternal; -extern LiveEventListDefaultTypeInternal _LiveEventList_default_instance_; -class Message; -struct MessageDefaultTypeInternal; -extern MessageDefaultTypeInternal _Message_default_instance_; -class Message_MetadataEntry_DoNotUse; -struct Message_MetadataEntry_DoNotUseDefaultTypeInternal; -extern Message_MetadataEntry_DoNotUseDefaultTypeInternal _Message_MetadataEntry_DoNotUse_default_instance_; -class Properties; -struct PropertiesDefaultTypeInternal; -extern PropertiesDefaultTypeInternal _Properties_default_instance_; -class Properties_ComputedEntry_DoNotUse; -struct Properties_ComputedEntry_DoNotUseDefaultTypeInternal; -extern Properties_ComputedEntry_DoNotUseDefaultTypeInternal _Properties_ComputedEntry_DoNotUse_default_instance_; -class Properties_CustomEntry_DoNotUse; -struct Properties_CustomEntry_DoNotUseDefaultTypeInternal; -extern Properties_CustomEntry_DoNotUseDefaultTypeInternal _Properties_CustomEntry_DoNotUse_default_instance_; -class Properties_DefaultEntry_DoNotUse; -struct Properties_DefaultEntry_DoNotUseDefaultTypeInternal; -extern Properties_DefaultEntry_DoNotUseDefaultTypeInternal _Properties_DefaultEntry_DoNotUse_default_instance_; -class Session; -struct SessionDefaultTypeInternal; -extern SessionDefaultTypeInternal _Session_default_instance_; -class UpdateMessageRequest; -struct UpdateMessageRequestDefaultTypeInternal; -extern UpdateMessageRequestDefaultTypeInternal _UpdateMessageRequest_default_instance_; -class UpdatePropertiesRequest; -struct UpdatePropertiesRequestDefaultTypeInternal; -extern UpdatePropertiesRequestDefaultTypeInternal _UpdatePropertiesRequest_default_instance_; -class UpdatePropertiesRequest_CustomEntry_DoNotUse; -struct UpdatePropertiesRequest_CustomEntry_DoNotUseDefaultTypeInternal; -extern UpdatePropertiesRequest_CustomEntry_DoNotUseDefaultTypeInternal _UpdatePropertiesRequest_CustomEntry_DoNotUse_default_instance_; -class UpdatePropertiesRequest_DefaultEntry_DoNotUse; -struct UpdatePropertiesRequest_DefaultEntry_DoNotUseDefaultTypeInternal; -extern UpdatePropertiesRequest_DefaultEntry_DoNotUseDefaultTypeInternal _UpdatePropertiesRequest_DefaultEntry_DoNotUse_default_instance_; -} // namespace api -} // namespace satori -namespace google { -namespace protobuf { -} // namespace protobuf -} // namespace google - -namespace satori { -namespace api { - -// =================================================================== - - -// ------------------------------------------------------------------- - -class UpdatePropertiesRequest_DefaultEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - UpdatePropertiesRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - UpdatePropertiesRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - UpdatePropertiesRequest_DefaultEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR UpdatePropertiesRequest_DefaultEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit UpdatePropertiesRequest_DefaultEntry_DoNotUse(::google::protobuf::Arena* arena); - static const UpdatePropertiesRequest_DefaultEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_UpdatePropertiesRequest_DefaultEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 64, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class UpdatePropertiesRequest_CustomEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - UpdatePropertiesRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - UpdatePropertiesRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - UpdatePropertiesRequest_CustomEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR UpdatePropertiesRequest_CustomEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit UpdatePropertiesRequest_CustomEntry_DoNotUse(::google::protobuf::Arena* arena); - static const UpdatePropertiesRequest_CustomEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_UpdatePropertiesRequest_CustomEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 63, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class UpdateMessageRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.UpdateMessageRequest) */ { - public: - inline UpdateMessageRequest() : UpdateMessageRequest(nullptr) {} - ~UpdateMessageRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR UpdateMessageRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline UpdateMessageRequest(const UpdateMessageRequest& from) : UpdateMessageRequest(nullptr, from) {} - inline UpdateMessageRequest(UpdateMessageRequest&& from) noexcept - : UpdateMessageRequest(nullptr, std::move(from)) {} - inline UpdateMessageRequest& operator=(const UpdateMessageRequest& from) { - CopyFrom(from); - return *this; - } - inline UpdateMessageRequest& operator=(UpdateMessageRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const UpdateMessageRequest& default_instance() { - return *internal_default_instance(); - } - static inline const UpdateMessageRequest* internal_default_instance() { - return reinterpret_cast( - &_UpdateMessageRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 32; - friend void swap(UpdateMessageRequest& a, UpdateMessageRequest& b) { a.Swap(&b); } - inline void Swap(UpdateMessageRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(UpdateMessageRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - UpdateMessageRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const UpdateMessageRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const UpdateMessageRequest& from) { UpdateMessageRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(UpdateMessageRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.UpdateMessageRequest"; } - - protected: - explicit UpdateMessageRequest(::google::protobuf::Arena* arena); - UpdateMessageRequest(::google::protobuf::Arena* arena, const UpdateMessageRequest& from); - UpdateMessageRequest(::google::protobuf::Arena* arena, UpdateMessageRequest&& from) noexcept - : UpdateMessageRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kIdFieldNumber = 1, - kReadTimeFieldNumber = 2, - kConsumeTimeFieldNumber = 3, - }; - // string id = 1; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // int64 read_time = 2; - void clear_read_time() ; - ::int64_t read_time() const; - void set_read_time(::int64_t value); - - private: - ::int64_t _internal_read_time() const; - void _internal_set_read_time(::int64_t value); - - public: - // int64 consume_time = 3; - void clear_consume_time() ; - ::int64_t consume_time() const; - void set_consume_time(::int64_t value); - - private: - ::int64_t _internal_consume_time() const; - void _internal_set_consume_time(::int64_t value); - - public: - // @@protoc_insertion_point(class_scope:satori.api.UpdateMessageRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 2, 3, 0, - 42, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_UpdateMessageRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const UpdateMessageRequest& from_msg); - ::google::protobuf::internal::ArenaStringPtr id_; - ::int64_t read_time_; - ::int64_t consume_time_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Properties_DefaultEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - Properties_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - Properties_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - Properties_DefaultEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR Properties_DefaultEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit Properties_DefaultEntry_DoNotUse(::google::protobuf::Arena* arena); - static const Properties_DefaultEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_Properties_DefaultEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 51, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Properties_CustomEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - Properties_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - Properties_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - Properties_CustomEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR Properties_CustomEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit Properties_CustomEntry_DoNotUse(::google::protobuf::Arena* arena); - static const Properties_CustomEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_Properties_CustomEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 50, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Properties_ComputedEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - Properties_ComputedEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - Properties_ComputedEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - Properties_ComputedEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR Properties_ComputedEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit Properties_ComputedEntry_DoNotUse(::google::protobuf::Arena* arena); - static const Properties_ComputedEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_Properties_ComputedEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 52, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Message_MetadataEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - Message_MetadataEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - Message_MetadataEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - Message_MetadataEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR Message_MetadataEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit Message_MetadataEntry_DoNotUse(::google::protobuf::Arena* arena); - static const Message_MetadataEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_Message_MetadataEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 49, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class LiveEvent final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.LiveEvent) */ { - public: - inline LiveEvent() : LiveEvent(nullptr) {} - ~LiveEvent() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR LiveEvent( - ::google::protobuf::internal::ConstantInitialized); - - inline LiveEvent(const LiveEvent& from) : LiveEvent(nullptr, from) {} - inline LiveEvent(LiveEvent&& from) noexcept - : LiveEvent(nullptr, std::move(from)) {} - inline LiveEvent& operator=(const LiveEvent& from) { - CopyFrom(from); - return *this; - } - inline LiveEvent& operator=(LiveEvent&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const LiveEvent& default_instance() { - return *internal_default_instance(); - } - static inline const LiveEvent* internal_default_instance() { - return reinterpret_cast( - &_LiveEvent_default_instance_); - } - static constexpr int kIndexInFileMessages = 18; - friend void swap(LiveEvent& a, LiveEvent& b) { a.Swap(&b); } - inline void Swap(LiveEvent* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(LiveEvent* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - LiveEvent* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const LiveEvent& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const LiveEvent& from) { LiveEvent::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(LiveEvent* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.LiveEvent"; } - - protected: - explicit LiveEvent(::google::protobuf::Arena* arena); - LiveEvent(::google::protobuf::Arena* arena, const LiveEvent& from); - LiveEvent(::google::protobuf::Arena* arena, LiveEvent&& from) noexcept - : LiveEvent(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNameFieldNumber = 1, - kDescriptionFieldNumber = 2, - kValueFieldNumber = 3, - kIdFieldNumber = 6, - kResetCronFieldNumber = 10, - kActiveStartTimeSecFieldNumber = 4, - kActiveEndTimeSecFieldNumber = 5, - kStartTimeSecFieldNumber = 7, - kEndTimeSecFieldNumber = 8, - kDurationSecFieldNumber = 9, - }; - // string name = 1; - void clear_name() ; - const std::string& name() const; - template - void set_name(Arg_&& arg, Args_... args); - std::string* mutable_name(); - PROTOBUF_NODISCARD std::string* release_name(); - void set_allocated_name(std::string* value); - - private: - const std::string& _internal_name() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( - const std::string& value); - std::string* _internal_mutable_name(); - - public: - // string description = 2; - void clear_description() ; - const std::string& description() const; - template - void set_description(Arg_&& arg, Args_... args); - std::string* mutable_description(); - PROTOBUF_NODISCARD std::string* release_description(); - void set_allocated_description(std::string* value); - - private: - const std::string& _internal_description() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_description( - const std::string& value); - std::string* _internal_mutable_description(); - - public: - // string value = 3; - void clear_value() ; - const std::string& value() const; - template - void set_value(Arg_&& arg, Args_... args); - std::string* mutable_value(); - PROTOBUF_NODISCARD std::string* release_value(); - void set_allocated_value(std::string* value); - - private: - const std::string& _internal_value() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_value( - const std::string& value); - std::string* _internal_mutable_value(); - - public: - // string id = 6; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // string reset_cron = 10; - void clear_reset_cron() ; - const std::string& reset_cron() const; - template - void set_reset_cron(Arg_&& arg, Args_... args); - std::string* mutable_reset_cron(); - PROTOBUF_NODISCARD std::string* release_reset_cron(); - void set_allocated_reset_cron(std::string* value); - - private: - const std::string& _internal_reset_cron() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_reset_cron( - const std::string& value); - std::string* _internal_mutable_reset_cron(); - - public: - // int64 active_start_time_sec = 4; - void clear_active_start_time_sec() ; - ::int64_t active_start_time_sec() const; - void set_active_start_time_sec(::int64_t value); - - private: - ::int64_t _internal_active_start_time_sec() const; - void _internal_set_active_start_time_sec(::int64_t value); - - public: - // int64 active_end_time_sec = 5; - void clear_active_end_time_sec() ; - ::int64_t active_end_time_sec() const; - void set_active_end_time_sec(::int64_t value); - - private: - ::int64_t _internal_active_end_time_sec() const; - void _internal_set_active_end_time_sec(::int64_t value); - - public: - // int64 start_time_sec = 7; - void clear_start_time_sec() ; - ::int64_t start_time_sec() const; - void set_start_time_sec(::int64_t value); - - private: - ::int64_t _internal_start_time_sec() const; - void _internal_set_start_time_sec(::int64_t value); - - public: - // int64 end_time_sec = 8; - void clear_end_time_sec() ; - ::int64_t end_time_sec() const; - void set_end_time_sec(::int64_t value); - - private: - ::int64_t _internal_end_time_sec() const; - void _internal_set_end_time_sec(::int64_t value); - - public: - // int64 duration_sec = 9; - void clear_duration_sec() ; - ::int64_t duration_sec() const; - void set_duration_sec(::int64_t value); - - private: - ::int64_t _internal_duration_sec() const; - void _internal_set_duration_sec(::int64_t value); - - public: - // @@protoc_insertion_point(class_scope:satori.api.LiveEvent) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 4, 10, 0, - 69, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_LiveEvent_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const LiveEvent& from_msg); - ::google::protobuf::internal::ArenaStringPtr name_; - ::google::protobuf::internal::ArenaStringPtr description_; - ::google::protobuf::internal::ArenaStringPtr value_; - ::google::protobuf::internal::ArenaStringPtr id_; - ::google::protobuf::internal::ArenaStringPtr reset_cron_; - ::int64_t active_start_time_sec_; - ::int64_t active_end_time_sec_; - ::int64_t start_time_sec_; - ::int64_t end_time_sec_; - ::int64_t duration_sec_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class IdentifyRequest_DefaultEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - IdentifyRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - IdentifyRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - IdentifyRequest_DefaultEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR IdentifyRequest_DefaultEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit IdentifyRequest_DefaultEntry_DoNotUse(::google::protobuf::Arena* arena); - static const IdentifyRequest_DefaultEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_IdentifyRequest_DefaultEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 56, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class IdentifyRequest_CustomEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - IdentifyRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - IdentifyRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - IdentifyRequest_CustomEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR IdentifyRequest_CustomEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit IdentifyRequest_CustomEntry_DoNotUse(::google::protobuf::Arena* arena); - static const IdentifyRequest_CustomEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_IdentifyRequest_CustomEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 55, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class GetMessageListRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.GetMessageListRequest) */ { - public: - inline GetMessageListRequest() : GetMessageListRequest(nullptr) {} - ~GetMessageListRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR GetMessageListRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline GetMessageListRequest(const GetMessageListRequest& from) : GetMessageListRequest(nullptr, from) {} - inline GetMessageListRequest(GetMessageListRequest&& from) noexcept - : GetMessageListRequest(nullptr, std::move(from)) {} - inline GetMessageListRequest& operator=(const GetMessageListRequest& from) { - CopyFrom(from); - return *this; - } - inline GetMessageListRequest& operator=(GetMessageListRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const GetMessageListRequest& default_instance() { - return *internal_default_instance(); - } - static inline const GetMessageListRequest* internal_default_instance() { - return reinterpret_cast( - &_GetMessageListRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 28; - friend void swap(GetMessageListRequest& a, GetMessageListRequest& b) { a.Swap(&b); } - inline void Swap(GetMessageListRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(GetMessageListRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - GetMessageListRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const GetMessageListRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const GetMessageListRequest& from) { GetMessageListRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(GetMessageListRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.GetMessageListRequest"; } - - protected: - explicit GetMessageListRequest(::google::protobuf::Arena* arena); - GetMessageListRequest(::google::protobuf::Arena* arena, const GetMessageListRequest& from); - GetMessageListRequest(::google::protobuf::Arena* arena, GetMessageListRequest&& from) noexcept - : GetMessageListRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kCursorFieldNumber = 3, - kLimitFieldNumber = 1, - kForwardFieldNumber = 2, - }; - // string cursor = 3; - void clear_cursor() ; - const std::string& cursor() const; - template - void set_cursor(Arg_&& arg, Args_... args); - std::string* mutable_cursor(); - PROTOBUF_NODISCARD std::string* release_cursor(); - void set_allocated_cursor(std::string* value); - - private: - const std::string& _internal_cursor() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_cursor( - const std::string& value); - std::string* _internal_mutable_cursor(); - - public: - // int32 limit = 1; - void clear_limit() ; - ::int32_t limit() const; - void set_limit(::int32_t value); - - private: - ::int32_t _internal_limit() const; - void _internal_set_limit(::int32_t value); - - public: - // bool forward = 2; - void clear_forward() ; - bool forward() const; - void set_forward(bool value); - - private: - bool _internal_forward() const; - void _internal_set_forward(bool value); - - public: - // @@protoc_insertion_point(class_scope:satori.api.GetMessageListRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 2, 3, 0, - 47, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_GetMessageListRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const GetMessageListRequest& from_msg); - ::google::protobuf::internal::ArenaStringPtr cursor_; - ::int32_t limit_; - bool forward_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class GetLiveEventsRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.GetLiveEventsRequest) */ { - public: - inline GetLiveEventsRequest() : GetLiveEventsRequest(nullptr) {} - ~GetLiveEventsRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR GetLiveEventsRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline GetLiveEventsRequest(const GetLiveEventsRequest& from) : GetLiveEventsRequest(nullptr, from) {} - inline GetLiveEventsRequest(GetLiveEventsRequest&& from) noexcept - : GetLiveEventsRequest(nullptr, std::move(from)) {} - inline GetLiveEventsRequest& operator=(const GetLiveEventsRequest& from) { - CopyFrom(from); - return *this; - } - inline GetLiveEventsRequest& operator=(GetLiveEventsRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const GetLiveEventsRequest& default_instance() { - return *internal_default_instance(); - } - static inline const GetLiveEventsRequest* internal_default_instance() { - return reinterpret_cast( - &_GetLiveEventsRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 14; - friend void swap(GetLiveEventsRequest& a, GetLiveEventsRequest& b) { a.Swap(&b); } - inline void Swap(GetLiveEventsRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(GetLiveEventsRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - GetLiveEventsRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const GetLiveEventsRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const GetLiveEventsRequest& from) { GetLiveEventsRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(GetLiveEventsRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.GetLiveEventsRequest"; } - - protected: - explicit GetLiveEventsRequest(::google::protobuf::Arena* arena); - GetLiveEventsRequest(::google::protobuf::Arena* arena, const GetLiveEventsRequest& from); - GetLiveEventsRequest(::google::protobuf::Arena* arena, GetLiveEventsRequest&& from) noexcept - : GetLiveEventsRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNamesFieldNumber = 1, - }; - // repeated string names = 1; - int names_size() const; - private: - int _internal_names_size() const; - - public: - void clear_names() ; - const std::string& names(int index) const; - std::string* mutable_names(int index); - template - void set_names(int index, Arg_&& value, Args_... args); - std::string* add_names(); - template - void add_names(Arg_&& value, Args_... args); - const ::google::protobuf::RepeatedPtrField& names() const; - ::google::protobuf::RepeatedPtrField* mutable_names(); - - private: - const ::google::protobuf::RepeatedPtrField& _internal_names() const; - ::google::protobuf::RepeatedPtrField* _internal_mutable_names(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.GetLiveEventsRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 0, - 45, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_GetLiveEventsRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const GetLiveEventsRequest& from_msg); - ::google::protobuf::RepeatedPtrField names_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class GetFlagsRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.GetFlagsRequest) */ { - public: - inline GetFlagsRequest() : GetFlagsRequest(nullptr) {} - ~GetFlagsRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR GetFlagsRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline GetFlagsRequest(const GetFlagsRequest& from) : GetFlagsRequest(nullptr, from) {} - inline GetFlagsRequest(GetFlagsRequest&& from) noexcept - : GetFlagsRequest(nullptr, std::move(from)) {} - inline GetFlagsRequest& operator=(const GetFlagsRequest& from) { - CopyFrom(from); - return *this; - } - inline GetFlagsRequest& operator=(GetFlagsRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const GetFlagsRequest& default_instance() { - return *internal_default_instance(); - } - static inline const GetFlagsRequest* internal_default_instance() { - return reinterpret_cast( - &_GetFlagsRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 13; - friend void swap(GetFlagsRequest& a, GetFlagsRequest& b) { a.Swap(&b); } - inline void Swap(GetFlagsRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(GetFlagsRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - GetFlagsRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const GetFlagsRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const GetFlagsRequest& from) { GetFlagsRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(GetFlagsRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.GetFlagsRequest"; } - - protected: - explicit GetFlagsRequest(::google::protobuf::Arena* arena); - GetFlagsRequest(::google::protobuf::Arena* arena, const GetFlagsRequest& from); - GetFlagsRequest(::google::protobuf::Arena* arena, GetFlagsRequest&& from) noexcept - : GetFlagsRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNamesFieldNumber = 1, - }; - // repeated string names = 1; - int names_size() const; - private: - int _internal_names_size() const; - - public: - void clear_names() ; - const std::string& names(int index) const; - std::string* mutable_names(int index); - template - void set_names(int index, Arg_&& value, Args_... args); - std::string* add_names(); - template - void add_names(Arg_&& value, Args_... args); - const ::google::protobuf::RepeatedPtrField& names() const; - ::google::protobuf::RepeatedPtrField* mutable_names(); - - private: - const ::google::protobuf::RepeatedPtrField& _internal_names() const; - ::google::protobuf::RepeatedPtrField* _internal_mutable_names(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.GetFlagsRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 0, - 40, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_GetFlagsRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const GetFlagsRequest& from_msg); - ::google::protobuf::RepeatedPtrField names_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class GetExperimentsRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.GetExperimentsRequest) */ { - public: - inline GetExperimentsRequest() : GetExperimentsRequest(nullptr) {} - ~GetExperimentsRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR GetExperimentsRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline GetExperimentsRequest(const GetExperimentsRequest& from) : GetExperimentsRequest(nullptr, from) {} - inline GetExperimentsRequest(GetExperimentsRequest&& from) noexcept - : GetExperimentsRequest(nullptr, std::move(from)) {} - inline GetExperimentsRequest& operator=(const GetExperimentsRequest& from) { - CopyFrom(from); - return *this; - } - inline GetExperimentsRequest& operator=(GetExperimentsRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const GetExperimentsRequest& default_instance() { - return *internal_default_instance(); - } - static inline const GetExperimentsRequest* internal_default_instance() { - return reinterpret_cast( - &_GetExperimentsRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 12; - friend void swap(GetExperimentsRequest& a, GetExperimentsRequest& b) { a.Swap(&b); } - inline void Swap(GetExperimentsRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(GetExperimentsRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - GetExperimentsRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const GetExperimentsRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const GetExperimentsRequest& from) { GetExperimentsRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(GetExperimentsRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.GetExperimentsRequest"; } - - protected: - explicit GetExperimentsRequest(::google::protobuf::Arena* arena); - GetExperimentsRequest(::google::protobuf::Arena* arena, const GetExperimentsRequest& from); - GetExperimentsRequest(::google::protobuf::Arena* arena, GetExperimentsRequest&& from) noexcept - : GetExperimentsRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNamesFieldNumber = 1, - }; - // repeated string names = 1; - int names_size() const; - private: - int _internal_names_size() const; - - public: - void clear_names() ; - const std::string& names(int index) const; - std::string* mutable_names(int index); - template - void set_names(int index, Arg_&& value, Args_... args); - std::string* add_names(); - template - void add_names(Arg_&& value, Args_... args); - const ::google::protobuf::RepeatedPtrField& names() const; - ::google::protobuf::RepeatedPtrField* mutable_names(); - - private: - const ::google::protobuf::RepeatedPtrField& _internal_names() const; - ::google::protobuf::RepeatedPtrField* _internal_mutable_names(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.GetExperimentsRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 0, - 46, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_GetExperimentsRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const GetExperimentsRequest& from_msg); - ::google::protobuf::RepeatedPtrField names_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Flag final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Flag) */ { - public: - inline Flag() : Flag(nullptr) {} - ~Flag() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Flag( - ::google::protobuf::internal::ConstantInitialized); - - inline Flag(const Flag& from) : Flag(nullptr, from) {} - inline Flag(Flag&& from) noexcept - : Flag(nullptr, std::move(from)) {} - inline Flag& operator=(const Flag& from) { - CopyFrom(from); - return *this; - } - inline Flag& operator=(Flag&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Flag& default_instance() { - return *internal_default_instance(); - } - static inline const Flag* internal_default_instance() { - return reinterpret_cast( - &_Flag_default_instance_); - } - static constexpr int kIndexInFileMessages = 10; - friend void swap(Flag& a, Flag& b) { a.Swap(&b); } - inline void Swap(Flag* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Flag* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Flag* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Flag& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Flag& from) { Flag::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Flag* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Flag"; } - - protected: - explicit Flag(::google::protobuf::Arena* arena); - Flag(::google::protobuf::Arena* arena, const Flag& from); - Flag(::google::protobuf::Arena* arena, Flag&& from) noexcept - : Flag(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNameFieldNumber = 1, - kValueFieldNumber = 2, - kConditionChangedFieldNumber = 3, - }; - // string name = 1; - void clear_name() ; - const std::string& name() const; - template - void set_name(Arg_&& arg, Args_... args); - std::string* mutable_name(); - PROTOBUF_NODISCARD std::string* release_name(); - void set_allocated_name(std::string* value); - - private: - const std::string& _internal_name() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( - const std::string& value); - std::string* _internal_mutable_name(); - - public: - // string value = 2; - void clear_value() ; - const std::string& value() const; - template - void set_value(Arg_&& arg, Args_... args); - std::string* mutable_value(); - PROTOBUF_NODISCARD std::string* release_value(); - void set_allocated_value(std::string* value); - - private: - const std::string& _internal_value() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_value( - const std::string& value); - std::string* _internal_mutable_value(); - - public: - // bool condition_changed = 3; - void clear_condition_changed() ; - bool condition_changed() const; - void set_condition_changed(bool value); - - private: - bool _internal_condition_changed() const; - void _internal_set_condition_changed(bool value); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Flag) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 2, 3, 0, - 33, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Flag_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Flag& from_msg); - ::google::protobuf::internal::ArenaStringPtr name_; - ::google::protobuf::internal::ArenaStringPtr value_; - bool condition_changed_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Experiment final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Experiment) */ { - public: - inline Experiment() : Experiment(nullptr) {} - ~Experiment() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Experiment( - ::google::protobuf::internal::ConstantInitialized); - - inline Experiment(const Experiment& from) : Experiment(nullptr, from) {} - inline Experiment(Experiment&& from) noexcept - : Experiment(nullptr, std::move(from)) {} - inline Experiment& operator=(const Experiment& from) { - CopyFrom(from); - return *this; - } - inline Experiment& operator=(Experiment&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Experiment& default_instance() { - return *internal_default_instance(); - } - static inline const Experiment* internal_default_instance() { - return reinterpret_cast( - &_Experiment_default_instance_); - } - static constexpr int kIndexInFileMessages = 8; - friend void swap(Experiment& a, Experiment& b) { a.Swap(&b); } - inline void Swap(Experiment* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Experiment* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Experiment* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Experiment& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Experiment& from) { Experiment::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Experiment* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Experiment"; } - - protected: - explicit Experiment(::google::protobuf::Arena* arena); - Experiment(::google::protobuf::Arena* arena, const Experiment& from); - Experiment(::google::protobuf::Arena* arena, Experiment&& from) noexcept - : Experiment(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kNameFieldNumber = 1, - kValueFieldNumber = 2, - }; - // string name = 1; - void clear_name() ; - const std::string& name() const; - template - void set_name(Arg_&& arg, Args_... args); - std::string* mutable_name(); - PROTOBUF_NODISCARD std::string* release_name(); - void set_allocated_name(std::string* value); - - private: - const std::string& _internal_name() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( - const std::string& value); - std::string* _internal_mutable_name(); - - public: - // string value = 2; - void clear_value() ; - const std::string& value() const; - template - void set_value(Arg_&& arg, Args_... args); - std::string* mutable_value(); - PROTOBUF_NODISCARD std::string* release_value(); - void set_allocated_value(std::string* value); - - private: - const std::string& _internal_value() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_value( - const std::string& value); - std::string* _internal_mutable_value(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Experiment) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 39, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Experiment_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Experiment& from_msg); - ::google::protobuf::internal::ArenaStringPtr name_; - ::google::protobuf::internal::ArenaStringPtr value_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Event_MetadataEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - Event_MetadataEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - Event_MetadataEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - Event_MetadataEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR Event_MetadataEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit Event_MetadataEntry_DoNotUse(::google::protobuf::Arena* arena); - static const Event_MetadataEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_Event_MetadataEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 47, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class DeleteMessageRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.DeleteMessageRequest) */ { - public: - inline DeleteMessageRequest() : DeleteMessageRequest(nullptr) {} - ~DeleteMessageRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR DeleteMessageRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline DeleteMessageRequest(const DeleteMessageRequest& from) : DeleteMessageRequest(nullptr, from) {} - inline DeleteMessageRequest(DeleteMessageRequest&& from) noexcept - : DeleteMessageRequest(nullptr, std::move(from)) {} - inline DeleteMessageRequest& operator=(const DeleteMessageRequest& from) { - CopyFrom(from); - return *this; - } - inline DeleteMessageRequest& operator=(DeleteMessageRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const DeleteMessageRequest& default_instance() { - return *internal_default_instance(); - } - static inline const DeleteMessageRequest* internal_default_instance() { - return reinterpret_cast( - &_DeleteMessageRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 33; - friend void swap(DeleteMessageRequest& a, DeleteMessageRequest& b) { a.Swap(&b); } - inline void Swap(DeleteMessageRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(DeleteMessageRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - DeleteMessageRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const DeleteMessageRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const DeleteMessageRequest& from) { DeleteMessageRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(DeleteMessageRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.DeleteMessageRequest"; } - - protected: - explicit DeleteMessageRequest(::google::protobuf::Arena* arena); - DeleteMessageRequest(::google::protobuf::Arena* arena, const DeleteMessageRequest& from); - DeleteMessageRequest(::google::protobuf::Arena* arena, DeleteMessageRequest&& from) noexcept - : DeleteMessageRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kIdFieldNumber = 1, - }; - // string id = 1; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.DeleteMessageRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 0, - 42, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_DeleteMessageRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const DeleteMessageRequest& from_msg); - ::google::protobuf::internal::ArenaStringPtr id_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class AuthenticateRequest_DefaultEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - AuthenticateRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - AuthenticateRequest_DefaultEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - AuthenticateRequest_DefaultEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR AuthenticateRequest_DefaultEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit AuthenticateRequest_DefaultEntry_DoNotUse(::google::protobuf::Arena* arena); - static const AuthenticateRequest_DefaultEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_AuthenticateRequest_DefaultEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 60, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class AuthenticateRequest_CustomEntry_DoNotUse final - : public ::google::protobuf::internal::MapEntry< - AuthenticateRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING> { - public: - using SuperType = ::google::protobuf::internal::MapEntry< - AuthenticateRequest_CustomEntry_DoNotUse, std::string, std::string, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING, - ::google::protobuf::internal::WireFormatLite::TYPE_STRING>; - AuthenticateRequest_CustomEntry_DoNotUse(); - template - explicit PROTOBUF_CONSTEXPR AuthenticateRequest_CustomEntry_DoNotUse( - ::google::protobuf::internal::ConstantInitialized); - explicit AuthenticateRequest_CustomEntry_DoNotUse(::google::protobuf::Arena* arena); - static const AuthenticateRequest_CustomEntry_DoNotUse* internal_default_instance() { - return reinterpret_cast( - &_AuthenticateRequest_CustomEntry_DoNotUse_default_instance_); - } - - - private: - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 59, 2> - _table_; - - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class AuthenticateRefreshRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.AuthenticateRefreshRequest) */ { - public: - inline AuthenticateRefreshRequest() : AuthenticateRefreshRequest(nullptr) {} - ~AuthenticateRefreshRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR AuthenticateRefreshRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline AuthenticateRefreshRequest(const AuthenticateRefreshRequest& from) : AuthenticateRefreshRequest(nullptr, from) {} - inline AuthenticateRefreshRequest(AuthenticateRefreshRequest&& from) noexcept - : AuthenticateRefreshRequest(nullptr, std::move(from)) {} - inline AuthenticateRefreshRequest& operator=(const AuthenticateRefreshRequest& from) { - CopyFrom(from); - return *this; - } - inline AuthenticateRefreshRequest& operator=(AuthenticateRefreshRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const AuthenticateRefreshRequest& default_instance() { - return *internal_default_instance(); - } - static inline const AuthenticateRefreshRequest* internal_default_instance() { - return reinterpret_cast( - &_AuthenticateRefreshRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 1; - friend void swap(AuthenticateRefreshRequest& a, AuthenticateRefreshRequest& b) { a.Swap(&b); } - inline void Swap(AuthenticateRefreshRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(AuthenticateRefreshRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - AuthenticateRefreshRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const AuthenticateRefreshRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const AuthenticateRefreshRequest& from) { AuthenticateRefreshRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(AuthenticateRefreshRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.AuthenticateRefreshRequest"; } - - protected: - explicit AuthenticateRefreshRequest(::google::protobuf::Arena* arena); - AuthenticateRefreshRequest(::google::protobuf::Arena* arena, const AuthenticateRefreshRequest& from); - AuthenticateRefreshRequest(::google::protobuf::Arena* arena, AuthenticateRefreshRequest&& from) noexcept - : AuthenticateRefreshRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kRefreshTokenFieldNumber = 1, - }; - // string refresh_token = 1; - void clear_refresh_token() ; - const std::string& refresh_token() const; - template - void set_refresh_token(Arg_&& arg, Args_... args); - std::string* mutable_refresh_token(); - PROTOBUF_NODISCARD std::string* release_refresh_token(); - void set_allocated_refresh_token(std::string* value); - - private: - const std::string& _internal_refresh_token() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_refresh_token( - const std::string& value); - std::string* _internal_mutable_refresh_token(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.AuthenticateRefreshRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 0, - 59, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_AuthenticateRefreshRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const AuthenticateRefreshRequest& from_msg); - ::google::protobuf::internal::ArenaStringPtr refresh_token_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class AuthenticateLogoutRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.AuthenticateLogoutRequest) */ { - public: - inline AuthenticateLogoutRequest() : AuthenticateLogoutRequest(nullptr) {} - ~AuthenticateLogoutRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR AuthenticateLogoutRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline AuthenticateLogoutRequest(const AuthenticateLogoutRequest& from) : AuthenticateLogoutRequest(nullptr, from) {} - inline AuthenticateLogoutRequest(AuthenticateLogoutRequest&& from) noexcept - : AuthenticateLogoutRequest(nullptr, std::move(from)) {} - inline AuthenticateLogoutRequest& operator=(const AuthenticateLogoutRequest& from) { - CopyFrom(from); - return *this; - } - inline AuthenticateLogoutRequest& operator=(AuthenticateLogoutRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const AuthenticateLogoutRequest& default_instance() { - return *internal_default_instance(); - } - static inline const AuthenticateLogoutRequest* internal_default_instance() { - return reinterpret_cast( - &_AuthenticateLogoutRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 0; - friend void swap(AuthenticateLogoutRequest& a, AuthenticateLogoutRequest& b) { a.Swap(&b); } - inline void Swap(AuthenticateLogoutRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(AuthenticateLogoutRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - AuthenticateLogoutRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const AuthenticateLogoutRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const AuthenticateLogoutRequest& from) { AuthenticateLogoutRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(AuthenticateLogoutRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.AuthenticateLogoutRequest"; } - - protected: - explicit AuthenticateLogoutRequest(::google::protobuf::Arena* arena); - AuthenticateLogoutRequest(::google::protobuf::Arena* arena, const AuthenticateLogoutRequest& from); - AuthenticateLogoutRequest(::google::protobuf::Arena* arena, AuthenticateLogoutRequest&& from) noexcept - : AuthenticateLogoutRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kTokenFieldNumber = 1, - kRefreshTokenFieldNumber = 2, - }; - // string token = 1; - void clear_token() ; - const std::string& token() const; - template - void set_token(Arg_&& arg, Args_... args); - std::string* mutable_token(); - PROTOBUF_NODISCARD std::string* release_token(); - void set_allocated_token(std::string* value); - - private: - const std::string& _internal_token() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_token( - const std::string& value); - std::string* _internal_mutable_token(); - - public: - // string refresh_token = 2; - void clear_refresh_token() ; - const std::string& refresh_token() const; - template - void set_refresh_token(Arg_&& arg, Args_... args); - std::string* mutable_refresh_token(); - PROTOBUF_NODISCARD std::string* release_refresh_token(); - void set_allocated_refresh_token(std::string* value); - - private: - const std::string& _internal_refresh_token() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_refresh_token( - const std::string& value); - std::string* _internal_mutable_refresh_token(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.AuthenticateLogoutRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 1, 2, 0, - 63, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_AuthenticateLogoutRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const AuthenticateLogoutRequest& from_msg); - ::google::protobuf::internal::ArenaStringPtr token_; - ::google::protobuf::internal::ArenaStringPtr refresh_token_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class UpdatePropertiesRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.UpdatePropertiesRequest) */ { - public: - inline UpdatePropertiesRequest() : UpdatePropertiesRequest(nullptr) {} - ~UpdatePropertiesRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR UpdatePropertiesRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline UpdatePropertiesRequest(const UpdatePropertiesRequest& from) : UpdatePropertiesRequest(nullptr, from) {} - inline UpdatePropertiesRequest(UpdatePropertiesRequest&& from) noexcept - : UpdatePropertiesRequest(nullptr, std::move(from)) {} - inline UpdatePropertiesRequest& operator=(const UpdatePropertiesRequest& from) { - CopyFrom(from); - return *this; - } - inline UpdatePropertiesRequest& operator=(UpdatePropertiesRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const UpdatePropertiesRequest& default_instance() { - return *internal_default_instance(); - } - static inline const UpdatePropertiesRequest* internal_default_instance() { - return reinterpret_cast( - &_UpdatePropertiesRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 27; - friend void swap(UpdatePropertiesRequest& a, UpdatePropertiesRequest& b) { a.Swap(&b); } - inline void Swap(UpdatePropertiesRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(UpdatePropertiesRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - UpdatePropertiesRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const UpdatePropertiesRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const UpdatePropertiesRequest& from) { UpdatePropertiesRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(UpdatePropertiesRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.UpdatePropertiesRequest"; } - - protected: - explicit UpdatePropertiesRequest(::google::protobuf::Arena* arena); - UpdatePropertiesRequest(::google::protobuf::Arena* arena, const UpdatePropertiesRequest& from); - UpdatePropertiesRequest(::google::protobuf::Arena* arena, UpdatePropertiesRequest&& from) noexcept - : UpdatePropertiesRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kDefaultFieldNumber = 1, - kCustomFieldNumber = 2, - kRecomputeFieldNumber = 3, - }; - // map default = 1; - int default__size() const; - private: - int _internal_default__size() const; - - public: - void clear_default_() ; - const ::google::protobuf::Map& default_() const; - ::google::protobuf::Map* mutable_default_(); - - private: - const ::google::protobuf::Map& _internal_default_() const; - ::google::protobuf::Map* _internal_mutable_default_(); - - public: - // map custom = 2; - int custom_size() const; - private: - int _internal_custom_size() const; - - public: - void clear_custom() ; - const ::google::protobuf::Map& custom() const; - ::google::protobuf::Map* mutable_custom(); - - private: - const ::google::protobuf::Map& _internal_custom() const; - ::google::protobuf::Map* _internal_mutable_custom(); - - public: - // .google.protobuf.BoolValue recompute = 3; - bool has_recompute() const; - void clear_recompute() ; - const ::google::protobuf::BoolValue& recompute() const; - PROTOBUF_NODISCARD ::google::protobuf::BoolValue* release_recompute(); - ::google::protobuf::BoolValue* mutable_recompute(); - void set_allocated_recompute(::google::protobuf::BoolValue* value); - void unsafe_arena_set_allocated_recompute(::google::protobuf::BoolValue* value); - ::google::protobuf::BoolValue* unsafe_arena_release_recompute(); - - private: - const ::google::protobuf::BoolValue& _internal_recompute() const; - ::google::protobuf::BoolValue* _internal_mutable_recompute(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.UpdatePropertiesRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 3, 3, - 56, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_UpdatePropertiesRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const UpdatePropertiesRequest& from_msg); - ::google::protobuf::internal::HasBits<1> _has_bits_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::MapField - default__; - ::google::protobuf::internal::MapField - custom_; - ::google::protobuf::BoolValue* recompute_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Properties final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Properties) */ { - public: - inline Properties() : Properties(nullptr) {} - ~Properties() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Properties( - ::google::protobuf::internal::ConstantInitialized); - - inline Properties(const Properties& from) : Properties(nullptr, from) {} - inline Properties(Properties&& from) noexcept - : Properties(nullptr, std::move(from)) {} - inline Properties& operator=(const Properties& from) { - CopyFrom(from); - return *this; - } - inline Properties& operator=(Properties&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Properties& default_instance() { - return *internal_default_instance(); - } - static inline const Properties* internal_default_instance() { - return reinterpret_cast( - &_Properties_default_instance_); - } - static constexpr int kIndexInFileMessages = 23; - friend void swap(Properties& a, Properties& b) { a.Swap(&b); } - inline void Swap(Properties* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Properties* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Properties* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Properties& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Properties& from) { Properties::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Properties* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Properties"; } - - protected: - explicit Properties(::google::protobuf::Arena* arena); - Properties(::google::protobuf::Arena* arena, const Properties& from); - Properties(::google::protobuf::Arena* arena, Properties&& from) noexcept - : Properties(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kDefaultFieldNumber = 1, - kComputedFieldNumber = 2, - kCustomFieldNumber = 3, - }; - // map default = 1; - int default__size() const; - private: - int _internal_default__size() const; - - public: - void clear_default_() ; - const ::google::protobuf::Map& default_() const; - ::google::protobuf::Map* mutable_default_(); - - private: - const ::google::protobuf::Map& _internal_default_() const; - ::google::protobuf::Map* _internal_mutable_default_(); - - public: - // map computed = 2; - int computed_size() const; - private: - int _internal_computed_size() const; - - public: - void clear_computed() ; - const ::google::protobuf::Map& computed() const; - ::google::protobuf::Map* mutable_computed(); - - private: - const ::google::protobuf::Map& _internal_computed() const; - ::google::protobuf::Map* _internal_mutable_computed(); - - public: - // map custom = 3; - int custom_size() const; - private: - int _internal_custom_size() const; - - public: - void clear_custom() ; - const ::google::protobuf::Map& custom() const; - ::google::protobuf::Map* mutable_custom(); - - private: - const ::google::protobuf::Map& _internal_custom() const; - ::google::protobuf::Map* _internal_mutable_custom(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Properties) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 3, 3, - 51, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Properties_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Properties& from_msg); - ::google::protobuf::internal::MapField - default__; - ::google::protobuf::internal::MapField - computed_; - ::google::protobuf::internal::MapField - custom_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Message final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Message) */ { - public: - inline Message() : Message(nullptr) {} - ~Message() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Message( - ::google::protobuf::internal::ConstantInitialized); - - inline Message(const Message& from) : Message(nullptr, from) {} - inline Message(Message&& from) noexcept - : Message(nullptr, std::move(from)) {} - inline Message& operator=(const Message& from) { - CopyFrom(from); - return *this; - } - inline Message& operator=(Message&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Message& default_instance() { - return *internal_default_instance(); - } - static inline const Message* internal_default_instance() { - return reinterpret_cast( - &_Message_default_instance_); - } - static constexpr int kIndexInFileMessages = 31; - friend void swap(Message& a, Message& b) { a.Swap(&b); } - inline void Swap(Message* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Message* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Message* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Message& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Message& from) { Message::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Message* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Message"; } - - protected: - explicit Message(::google::protobuf::Arena* arena); - Message(::google::protobuf::Arena* arena, const Message& from); - Message(::google::protobuf::Arena* arena, Message&& from) noexcept - : Message(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kMetadataFieldNumber = 3, - kScheduleIdFieldNumber = 1, - kTextFieldNumber = 8, - kIdFieldNumber = 9, - kTitleFieldNumber = 10, - kImageUrlFieldNumber = 11, - kSendTimeFieldNumber = 2, - kCreateTimeFieldNumber = 4, - kUpdateTimeFieldNumber = 5, - kReadTimeFieldNumber = 6, - kConsumeTimeFieldNumber = 7, - }; - // map metadata = 3; - int metadata_size() const; - private: - int _internal_metadata_size() const; - - public: - void clear_metadata() ; - const ::google::protobuf::Map& metadata() const; - ::google::protobuf::Map* mutable_metadata(); - - private: - const ::google::protobuf::Map& _internal_metadata() const; - ::google::protobuf::Map* _internal_mutable_metadata(); - - public: - // string schedule_id = 1; - void clear_schedule_id() ; - const std::string& schedule_id() const; - template - void set_schedule_id(Arg_&& arg, Args_... args); - std::string* mutable_schedule_id(); - PROTOBUF_NODISCARD std::string* release_schedule_id(); - void set_allocated_schedule_id(std::string* value); - - private: - const std::string& _internal_schedule_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_schedule_id( - const std::string& value); - std::string* _internal_mutable_schedule_id(); - - public: - // string text = 8; - void clear_text() ; - const std::string& text() const; - template - void set_text(Arg_&& arg, Args_... args); - std::string* mutable_text(); - PROTOBUF_NODISCARD std::string* release_text(); - void set_allocated_text(std::string* value); - - private: - const std::string& _internal_text() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_text( - const std::string& value); - std::string* _internal_mutable_text(); - - public: - // string id = 9; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // string title = 10; - void clear_title() ; - const std::string& title() const; - template - void set_title(Arg_&& arg, Args_... args); - std::string* mutable_title(); - PROTOBUF_NODISCARD std::string* release_title(); - void set_allocated_title(std::string* value); - - private: - const std::string& _internal_title() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_title( - const std::string& value); - std::string* _internal_mutable_title(); - - public: - // string image_url = 11; - void clear_image_url() ; - const std::string& image_url() const; - template - void set_image_url(Arg_&& arg, Args_... args); - std::string* mutable_image_url(); - PROTOBUF_NODISCARD std::string* release_image_url(); - void set_allocated_image_url(std::string* value); - - private: - const std::string& _internal_image_url() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_image_url( - const std::string& value); - std::string* _internal_mutable_image_url(); - - public: - // int64 send_time = 2; - void clear_send_time() ; - ::int64_t send_time() const; - void set_send_time(::int64_t value); - - private: - ::int64_t _internal_send_time() const; - void _internal_set_send_time(::int64_t value); - - public: - // int64 create_time = 4; - void clear_create_time() ; - ::int64_t create_time() const; - void set_create_time(::int64_t value); - - private: - ::int64_t _internal_create_time() const; - void _internal_set_create_time(::int64_t value); - - public: - // int64 update_time = 5; - void clear_update_time() ; - ::int64_t update_time() const; - void set_update_time(::int64_t value); - - private: - ::int64_t _internal_update_time() const; - void _internal_set_update_time(::int64_t value); - - public: - // int64 read_time = 6; - void clear_read_time() ; - ::int64_t read_time() const; - void set_read_time(::int64_t value); - - private: - ::int64_t _internal_read_time() const; - void _internal_set_read_time(::int64_t value); - - public: - // int64 consume_time = 7; - void clear_consume_time() ; - ::int64_t consume_time() const; - void set_consume_time(::int64_t value); - - private: - ::int64_t _internal_consume_time() const; - void _internal_set_consume_time(::int64_t value); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Message) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 4, 11, 1, - 74, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Message_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Message& from_msg); - ::google::protobuf::internal::MapField - metadata_; - ::google::protobuf::internal::ArenaStringPtr schedule_id_; - ::google::protobuf::internal::ArenaStringPtr text_; - ::google::protobuf::internal::ArenaStringPtr id_; - ::google::protobuf::internal::ArenaStringPtr title_; - ::google::protobuf::internal::ArenaStringPtr image_url_; - ::int64_t send_time_; - ::int64_t create_time_; - ::int64_t update_time_; - ::int64_t read_time_; - ::int64_t consume_time_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class LiveEventList final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.LiveEventList) */ { - public: - inline LiveEventList() : LiveEventList(nullptr) {} - ~LiveEventList() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR LiveEventList( - ::google::protobuf::internal::ConstantInitialized); - - inline LiveEventList(const LiveEventList& from) : LiveEventList(nullptr, from) {} - inline LiveEventList(LiveEventList&& from) noexcept - : LiveEventList(nullptr, std::move(from)) {} - inline LiveEventList& operator=(const LiveEventList& from) { - CopyFrom(from); - return *this; - } - inline LiveEventList& operator=(LiveEventList&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const LiveEventList& default_instance() { - return *internal_default_instance(); - } - static inline const LiveEventList* internal_default_instance() { - return reinterpret_cast( - &_LiveEventList_default_instance_); - } - static constexpr int kIndexInFileMessages = 19; - friend void swap(LiveEventList& a, LiveEventList& b) { a.Swap(&b); } - inline void Swap(LiveEventList* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(LiveEventList* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - LiveEventList* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const LiveEventList& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const LiveEventList& from) { LiveEventList::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(LiveEventList* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.LiveEventList"; } - - protected: - explicit LiveEventList(::google::protobuf::Arena* arena); - LiveEventList(::google::protobuf::Arena* arena, const LiveEventList& from); - LiveEventList(::google::protobuf::Arena* arena, LiveEventList&& from) noexcept - : LiveEventList(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kLiveEventsFieldNumber = 1, - }; - // repeated .satori.api.LiveEvent live_events = 1; - int live_events_size() const; - private: - int _internal_live_events_size() const; - - public: - void clear_live_events() ; - ::satori::api::LiveEvent* mutable_live_events(int index); - ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>* mutable_live_events(); - - private: - const ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>& _internal_live_events() const; - ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>* _internal_mutable_live_events(); - public: - const ::satori::api::LiveEvent& live_events(int index) const; - ::satori::api::LiveEvent* add_live_events(); - const ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>& live_events() const; - // @@protoc_insertion_point(class_scope:satori.api.LiveEventList) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 1, - 0, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_LiveEventList_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const LiveEventList& from_msg); - ::google::protobuf::RepeatedPtrField< ::satori::api::LiveEvent > live_events_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class IdentifyRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.IdentifyRequest) */ { - public: - inline IdentifyRequest() : IdentifyRequest(nullptr) {} - ~IdentifyRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR IdentifyRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline IdentifyRequest(const IdentifyRequest& from) : IdentifyRequest(nullptr, from) {} - inline IdentifyRequest(IdentifyRequest&& from) noexcept - : IdentifyRequest(nullptr, std::move(from)) {} - inline IdentifyRequest& operator=(const IdentifyRequest& from) { - CopyFrom(from); - return *this; - } - inline IdentifyRequest& operator=(IdentifyRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const IdentifyRequest& default_instance() { - return *internal_default_instance(); - } - static inline const IdentifyRequest* internal_default_instance() { - return reinterpret_cast( - &_IdentifyRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 17; - friend void swap(IdentifyRequest& a, IdentifyRequest& b) { a.Swap(&b); } - inline void Swap(IdentifyRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(IdentifyRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - IdentifyRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const IdentifyRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const IdentifyRequest& from) { IdentifyRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(IdentifyRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.IdentifyRequest"; } - - protected: - explicit IdentifyRequest(::google::protobuf::Arena* arena); - IdentifyRequest(::google::protobuf::Arena* arena, const IdentifyRequest& from); - IdentifyRequest(::google::protobuf::Arena* arena, IdentifyRequest&& from) noexcept - : IdentifyRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kDefaultFieldNumber = 2, - kCustomFieldNumber = 3, - kIdFieldNumber = 1, - }; - // map default = 2; - int default__size() const; - private: - int _internal_default__size() const; - - public: - void clear_default_() ; - const ::google::protobuf::Map& default_() const; - ::google::protobuf::Map* mutable_default_(); - - private: - const ::google::protobuf::Map& _internal_default_() const; - ::google::protobuf::Map* _internal_mutable_default_(); - - public: - // map custom = 3; - int custom_size() const; - private: - int _internal_custom_size() const; - - public: - void clear_custom() ; - const ::google::protobuf::Map& custom() const; - ::google::protobuf::Map* mutable_custom(); - - private: - const ::google::protobuf::Map& _internal_custom() const; - ::google::protobuf::Map* _internal_mutable_custom(); - - public: - // string id = 1; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.IdentifyRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 3, 2, - 50, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_IdentifyRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const IdentifyRequest& from_msg); - ::google::protobuf::internal::MapField - default__; - ::google::protobuf::internal::MapField - custom_; - ::google::protobuf::internal::ArenaStringPtr id_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class FlagList final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.FlagList) */ { - public: - inline FlagList() : FlagList(nullptr) {} - ~FlagList() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR FlagList( - ::google::protobuf::internal::ConstantInitialized); - - inline FlagList(const FlagList& from) : FlagList(nullptr, from) {} - inline FlagList(FlagList&& from) noexcept - : FlagList(nullptr, std::move(from)) {} - inline FlagList& operator=(const FlagList& from) { - CopyFrom(from); - return *this; - } - inline FlagList& operator=(FlagList&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const FlagList& default_instance() { - return *internal_default_instance(); - } - static inline const FlagList* internal_default_instance() { - return reinterpret_cast( - &_FlagList_default_instance_); - } - static constexpr int kIndexInFileMessages = 11; - friend void swap(FlagList& a, FlagList& b) { a.Swap(&b); } - inline void Swap(FlagList* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(FlagList* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - FlagList* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const FlagList& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const FlagList& from) { FlagList::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(FlagList* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.FlagList"; } - - protected: - explicit FlagList(::google::protobuf::Arena* arena); - FlagList(::google::protobuf::Arena* arena, const FlagList& from); - FlagList(::google::protobuf::Arena* arena, FlagList&& from) noexcept - : FlagList(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kFlagsFieldNumber = 1, - }; - // repeated .satori.api.Flag flags = 1; - int flags_size() const; - private: - int _internal_flags_size() const; - - public: - void clear_flags() ; - ::satori::api::Flag* mutable_flags(int index); - ::google::protobuf::RepeatedPtrField<::satori::api::Flag>* mutable_flags(); - - private: - const ::google::protobuf::RepeatedPtrField<::satori::api::Flag>& _internal_flags() const; - ::google::protobuf::RepeatedPtrField<::satori::api::Flag>* _internal_mutable_flags(); - public: - const ::satori::api::Flag& flags(int index) const; - ::satori::api::Flag* add_flags(); - const ::google::protobuf::RepeatedPtrField<::satori::api::Flag>& flags() const; - // @@protoc_insertion_point(class_scope:satori.api.FlagList) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 1, - 0, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_FlagList_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const FlagList& from_msg); - ::google::protobuf::RepeatedPtrField< ::satori::api::Flag > flags_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class ExperimentList final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.ExperimentList) */ { - public: - inline ExperimentList() : ExperimentList(nullptr) {} - ~ExperimentList() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR ExperimentList( - ::google::protobuf::internal::ConstantInitialized); - - inline ExperimentList(const ExperimentList& from) : ExperimentList(nullptr, from) {} - inline ExperimentList(ExperimentList&& from) noexcept - : ExperimentList(nullptr, std::move(from)) {} - inline ExperimentList& operator=(const ExperimentList& from) { - CopyFrom(from); - return *this; - } - inline ExperimentList& operator=(ExperimentList&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const ExperimentList& default_instance() { - return *internal_default_instance(); - } - static inline const ExperimentList* internal_default_instance() { - return reinterpret_cast( - &_ExperimentList_default_instance_); - } - static constexpr int kIndexInFileMessages = 9; - friend void swap(ExperimentList& a, ExperimentList& b) { a.Swap(&b); } - inline void Swap(ExperimentList* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(ExperimentList* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - ExperimentList* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const ExperimentList& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const ExperimentList& from) { ExperimentList::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(ExperimentList* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.ExperimentList"; } - - protected: - explicit ExperimentList(::google::protobuf::Arena* arena); - ExperimentList(::google::protobuf::Arena* arena, const ExperimentList& from); - ExperimentList(::google::protobuf::Arena* arena, ExperimentList&& from) noexcept - : ExperimentList(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kExperimentsFieldNumber = 1, - }; - // repeated .satori.api.Experiment experiments = 1; - int experiments_size() const; - private: - int _internal_experiments_size() const; - - public: - void clear_experiments() ; - ::satori::api::Experiment* mutable_experiments(int index); - ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>* mutable_experiments(); - - private: - const ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>& _internal_experiments() const; - ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>* _internal_mutable_experiments(); - public: - const ::satori::api::Experiment& experiments(int index) const; - ::satori::api::Experiment* add_experiments(); - const ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>& experiments() const; - // @@protoc_insertion_point(class_scope:satori.api.ExperimentList) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 1, - 0, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_ExperimentList_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const ExperimentList& from_msg); - ::google::protobuf::RepeatedPtrField< ::satori::api::Experiment > experiments_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Event final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Event) */ { - public: - inline Event() : Event(nullptr) {} - ~Event() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Event( - ::google::protobuf::internal::ConstantInitialized); - - inline Event(const Event& from) : Event(nullptr, from) {} - inline Event(Event&& from) noexcept - : Event(nullptr, std::move(from)) {} - inline Event& operator=(const Event& from) { - CopyFrom(from); - return *this; - } - inline Event& operator=(Event&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Event& default_instance() { - return *internal_default_instance(); - } - static inline const Event* internal_default_instance() { - return reinterpret_cast( - &_Event_default_instance_); - } - static constexpr int kIndexInFileMessages = 6; - friend void swap(Event& a, Event& b) { a.Swap(&b); } - inline void Swap(Event* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Event* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Event* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Event& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Event& from) { Event::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Event* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Event"; } - - protected: - explicit Event(::google::protobuf::Arena* arena); - Event(::google::protobuf::Arena* arena, const Event& from); - Event(::google::protobuf::Arena* arena, Event&& from) noexcept - : Event(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kMetadataFieldNumber = 3, - kNameFieldNumber = 1, - kIdFieldNumber = 2, - kValueFieldNumber = 4, - kTimestampFieldNumber = 5, - }; - // map metadata = 3; - int metadata_size() const; - private: - int _internal_metadata_size() const; - - public: - void clear_metadata() ; - const ::google::protobuf::Map& metadata() const; - ::google::protobuf::Map* mutable_metadata(); - - private: - const ::google::protobuf::Map& _internal_metadata() const; - ::google::protobuf::Map* _internal_mutable_metadata(); - - public: - // string name = 1; - void clear_name() ; - const std::string& name() const; - template - void set_name(Arg_&& arg, Args_... args); - std::string* mutable_name(); - PROTOBUF_NODISCARD std::string* release_name(); - void set_allocated_name(std::string* value); - - private: - const std::string& _internal_name() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_name( - const std::string& value); - std::string* _internal_mutable_name(); - - public: - // string id = 2; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // string value = 4; - void clear_value() ; - const std::string& value() const; - template - void set_value(Arg_&& arg, Args_... args); - std::string* mutable_value(); - PROTOBUF_NODISCARD std::string* release_value(); - void set_allocated_value(std::string* value); - - private: - const std::string& _internal_value() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_value( - const std::string& value); - std::string* _internal_mutable_value(); - - public: - // .google.protobuf.Timestamp timestamp = 5; - bool has_timestamp() const; - void clear_timestamp() ; - const ::google::protobuf::Timestamp& timestamp() const; - PROTOBUF_NODISCARD ::google::protobuf::Timestamp* release_timestamp(); - ::google::protobuf::Timestamp* mutable_timestamp(); - void set_allocated_timestamp(::google::protobuf::Timestamp* value); - void unsafe_arena_set_allocated_timestamp(::google::protobuf::Timestamp* value); - ::google::protobuf::Timestamp* unsafe_arena_release_timestamp(); - - private: - const ::google::protobuf::Timestamp& _internal_timestamp() const; - ::google::protobuf::Timestamp* _internal_mutable_timestamp(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Event) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 3, 5, 2, - 44, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Event_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Event& from_msg); - ::google::protobuf::internal::HasBits<1> _has_bits_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::MapField - metadata_; - ::google::protobuf::internal::ArenaStringPtr name_; - ::google::protobuf::internal::ArenaStringPtr id_; - ::google::protobuf::internal::ArenaStringPtr value_; - ::google::protobuf::Timestamp* timestamp_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class AuthenticateRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.AuthenticateRequest) */ { - public: - inline AuthenticateRequest() : AuthenticateRequest(nullptr) {} - ~AuthenticateRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR AuthenticateRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline AuthenticateRequest(const AuthenticateRequest& from) : AuthenticateRequest(nullptr, from) {} - inline AuthenticateRequest(AuthenticateRequest&& from) noexcept - : AuthenticateRequest(nullptr, std::move(from)) {} - inline AuthenticateRequest& operator=(const AuthenticateRequest& from) { - CopyFrom(from); - return *this; - } - inline AuthenticateRequest& operator=(AuthenticateRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const AuthenticateRequest& default_instance() { - return *internal_default_instance(); - } - static inline const AuthenticateRequest* internal_default_instance() { - return reinterpret_cast( - &_AuthenticateRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 4; - friend void swap(AuthenticateRequest& a, AuthenticateRequest& b) { a.Swap(&b); } - inline void Swap(AuthenticateRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(AuthenticateRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - AuthenticateRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const AuthenticateRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const AuthenticateRequest& from) { AuthenticateRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(AuthenticateRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.AuthenticateRequest"; } - - protected: - explicit AuthenticateRequest(::google::protobuf::Arena* arena); - AuthenticateRequest(::google::protobuf::Arena* arena, const AuthenticateRequest& from); - AuthenticateRequest(::google::protobuf::Arena* arena, AuthenticateRequest&& from) noexcept - : AuthenticateRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kDefaultFieldNumber = 2, - kCustomFieldNumber = 3, - kIdFieldNumber = 1, - }; - // map default = 2; - int default__size() const; - private: - int _internal_default__size() const; - - public: - void clear_default_() ; - const ::google::protobuf::Map& default_() const; - ::google::protobuf::Map* mutable_default_(); - - private: - const ::google::protobuf::Map& _internal_default_() const; - ::google::protobuf::Map* _internal_mutable_default_(); - - public: - // map custom = 3; - int custom_size() const; - private: - int _internal_custom_size() const; - - public: - void clear_custom() ; - const ::google::protobuf::Map& custom() const; - ::google::protobuf::Map* mutable_custom(); - - private: - const ::google::protobuf::Map& _internal_custom() const; - ::google::protobuf::Map* _internal_mutable_custom(); - - public: - // string id = 1; - void clear_id() ; - const std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - std::string* mutable_id(); - PROTOBUF_NODISCARD std::string* release_id(); - void set_allocated_id(std::string* value); - - private: - const std::string& _internal_id() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_id( - const std::string& value); - std::string* _internal_mutable_id(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.AuthenticateRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 3, 2, - 54, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_AuthenticateRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const AuthenticateRequest& from_msg); - ::google::protobuf::internal::MapField - default__; - ::google::protobuf::internal::MapField - custom_; - ::google::protobuf::internal::ArenaStringPtr id_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class Session final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.Session) */ { - public: - inline Session() : Session(nullptr) {} - ~Session() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR Session( - ::google::protobuf::internal::ConstantInitialized); - - inline Session(const Session& from) : Session(nullptr, from) {} - inline Session(Session&& from) noexcept - : Session(nullptr, std::move(from)) {} - inline Session& operator=(const Session& from) { - CopyFrom(from); - return *this; - } - inline Session& operator=(Session&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const Session& default_instance() { - return *internal_default_instance(); - } - static inline const Session* internal_default_instance() { - return reinterpret_cast( - &_Session_default_instance_); - } - static constexpr int kIndexInFileMessages = 24; - friend void swap(Session& a, Session& b) { a.Swap(&b); } - inline void Swap(Session* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(Session* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - Session* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const Session& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const Session& from) { Session::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(Session* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.Session"; } - - protected: - explicit Session(::google::protobuf::Arena* arena); - Session(::google::protobuf::Arena* arena, const Session& from); - Session(::google::protobuf::Arena* arena, Session&& from) noexcept - : Session(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kTokenFieldNumber = 1, - kRefreshTokenFieldNumber = 2, - kPropertiesFieldNumber = 3, - }; - // string token = 1; - void clear_token() ; - const std::string& token() const; - template - void set_token(Arg_&& arg, Args_... args); - std::string* mutable_token(); - PROTOBUF_NODISCARD std::string* release_token(); - void set_allocated_token(std::string* value); - - private: - const std::string& _internal_token() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_token( - const std::string& value); - std::string* _internal_mutable_token(); - - public: - // string refresh_token = 2; - void clear_refresh_token() ; - const std::string& refresh_token() const; - template - void set_refresh_token(Arg_&& arg, Args_... args); - std::string* mutable_refresh_token(); - PROTOBUF_NODISCARD std::string* release_refresh_token(); - void set_allocated_refresh_token(std::string* value); - - private: - const std::string& _internal_refresh_token() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_refresh_token( - const std::string& value); - std::string* _internal_mutable_refresh_token(); - - public: - // .satori.api.Properties properties = 3; - bool has_properties() const; - void clear_properties() ; - const ::satori::api::Properties& properties() const; - PROTOBUF_NODISCARD ::satori::api::Properties* release_properties(); - ::satori::api::Properties* mutable_properties(); - void set_allocated_properties(::satori::api::Properties* value); - void unsafe_arena_set_allocated_properties(::satori::api::Properties* value); - ::satori::api::Properties* unsafe_arena_release_properties(); - - private: - const ::satori::api::Properties& _internal_properties() const; - ::satori::api::Properties* _internal_mutable_properties(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.Session) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 2, 3, 1, - 45, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_Session_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const Session& from_msg); - ::google::protobuf::internal::HasBits<1> _has_bits_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::ArenaStringPtr token_; - ::google::protobuf::internal::ArenaStringPtr refresh_token_; - ::satori::api::Properties* properties_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class GetMessageListResponse final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.GetMessageListResponse) */ { - public: - inline GetMessageListResponse() : GetMessageListResponse(nullptr) {} - ~GetMessageListResponse() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR GetMessageListResponse( - ::google::protobuf::internal::ConstantInitialized); - - inline GetMessageListResponse(const GetMessageListResponse& from) : GetMessageListResponse(nullptr, from) {} - inline GetMessageListResponse(GetMessageListResponse&& from) noexcept - : GetMessageListResponse(nullptr, std::move(from)) {} - inline GetMessageListResponse& operator=(const GetMessageListResponse& from) { - CopyFrom(from); - return *this; - } - inline GetMessageListResponse& operator=(GetMessageListResponse&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const GetMessageListResponse& default_instance() { - return *internal_default_instance(); - } - static inline const GetMessageListResponse* internal_default_instance() { - return reinterpret_cast( - &_GetMessageListResponse_default_instance_); - } - static constexpr int kIndexInFileMessages = 29; - friend void swap(GetMessageListResponse& a, GetMessageListResponse& b) { a.Swap(&b); } - inline void Swap(GetMessageListResponse* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(GetMessageListResponse* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - GetMessageListResponse* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const GetMessageListResponse& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const GetMessageListResponse& from) { GetMessageListResponse::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(GetMessageListResponse* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.GetMessageListResponse"; } - - protected: - explicit GetMessageListResponse(::google::protobuf::Arena* arena); - GetMessageListResponse(::google::protobuf::Arena* arena, const GetMessageListResponse& from); - GetMessageListResponse(::google::protobuf::Arena* arena, GetMessageListResponse&& from) noexcept - : GetMessageListResponse(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kMessagesFieldNumber = 1, - kNextCursorFieldNumber = 2, - kPrevCursorFieldNumber = 3, - kCacheableCursorFieldNumber = 4, - }; - // repeated .satori.api.Message messages = 1; - int messages_size() const; - private: - int _internal_messages_size() const; - - public: - void clear_messages() ; - ::satori::api::Message* mutable_messages(int index); - ::google::protobuf::RepeatedPtrField<::satori::api::Message>* mutable_messages(); - - private: - const ::google::protobuf::RepeatedPtrField<::satori::api::Message>& _internal_messages() const; - ::google::protobuf::RepeatedPtrField<::satori::api::Message>* _internal_mutable_messages(); - public: - const ::satori::api::Message& messages(int index) const; - ::satori::api::Message* add_messages(); - const ::google::protobuf::RepeatedPtrField<::satori::api::Message>& messages() const; - // string next_cursor = 2; - void clear_next_cursor() ; - const std::string& next_cursor() const; - template - void set_next_cursor(Arg_&& arg, Args_... args); - std::string* mutable_next_cursor(); - PROTOBUF_NODISCARD std::string* release_next_cursor(); - void set_allocated_next_cursor(std::string* value); - - private: - const std::string& _internal_next_cursor() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_next_cursor( - const std::string& value); - std::string* _internal_mutable_next_cursor(); - - public: - // string prev_cursor = 3; - void clear_prev_cursor() ; - const std::string& prev_cursor() const; - template - void set_prev_cursor(Arg_&& arg, Args_... args); - std::string* mutable_prev_cursor(); - PROTOBUF_NODISCARD std::string* release_prev_cursor(); - void set_allocated_prev_cursor(std::string* value); - - private: - const std::string& _internal_prev_cursor() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_prev_cursor( - const std::string& value); - std::string* _internal_mutable_prev_cursor(); - - public: - // string cacheable_cursor = 4; - void clear_cacheable_cursor() ; - const std::string& cacheable_cursor() const; - template - void set_cacheable_cursor(Arg_&& arg, Args_... args); - std::string* mutable_cacheable_cursor(); - PROTOBUF_NODISCARD std::string* release_cacheable_cursor(); - void set_allocated_cacheable_cursor(std::string* value); - - private: - const std::string& _internal_cacheable_cursor() const; - inline PROTOBUF_ALWAYS_INLINE void _internal_set_cacheable_cursor( - const std::string& value); - std::string* _internal_mutable_cacheable_cursor(); - - public: - // @@protoc_insertion_point(class_scope:satori.api.GetMessageListResponse) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 2, 4, 1, - 80, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_GetMessageListResponse_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const GetMessageListResponse& from_msg); - ::google::protobuf::RepeatedPtrField< ::satori::api::Message > messages_; - ::google::protobuf::internal::ArenaStringPtr next_cursor_; - ::google::protobuf::internal::ArenaStringPtr prev_cursor_; - ::google::protobuf::internal::ArenaStringPtr cacheable_cursor_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; -// ------------------------------------------------------------------- - -class EventRequest final : public ::google::protobuf::Message -/* @@protoc_insertion_point(class_definition:satori.api.EventRequest) */ { - public: - inline EventRequest() : EventRequest(nullptr) {} - ~EventRequest() PROTOBUF_FINAL; - template - explicit PROTOBUF_CONSTEXPR EventRequest( - ::google::protobuf::internal::ConstantInitialized); - - inline EventRequest(const EventRequest& from) : EventRequest(nullptr, from) {} - inline EventRequest(EventRequest&& from) noexcept - : EventRequest(nullptr, std::move(from)) {} - inline EventRequest& operator=(const EventRequest& from) { - CopyFrom(from); - return *this; - } - inline EventRequest& operator=(EventRequest&& from) noexcept { - if (this == &from) return *this; - if (GetArena() == from.GetArena() -#ifdef PROTOBUF_FORCE_COPY_IN_MOVE - && GetArena() != nullptr -#endif // !PROTOBUF_FORCE_COPY_IN_MOVE - ) { - InternalSwap(&from); - } else { - CopyFrom(from); - } - return *this; - } - - inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); - } - inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); - } - - static const ::google::protobuf::Descriptor* descriptor() { - return GetDescriptor(); - } - static const ::google::protobuf::Descriptor* GetDescriptor() { - return default_instance().GetMetadata().descriptor; - } - static const ::google::protobuf::Reflection* GetReflection() { - return default_instance().GetMetadata().reflection; - } - static const EventRequest& default_instance() { - return *internal_default_instance(); - } - static inline const EventRequest* internal_default_instance() { - return reinterpret_cast( - &_EventRequest_default_instance_); - } - static constexpr int kIndexInFileMessages = 7; - friend void swap(EventRequest& a, EventRequest& b) { a.Swap(&b); } - inline void Swap(EventRequest* other) { - if (other == this) return; -#ifdef PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() != nullptr && GetArena() == other->GetArena()) { -#else // PROTOBUF_FORCE_COPY_IN_SWAP - if (GetArena() == other->GetArena()) { -#endif // !PROTOBUF_FORCE_COPY_IN_SWAP - InternalSwap(other); - } else { - ::google::protobuf::internal::GenericSwap(this, other); - } - } - void UnsafeArenaSwap(EventRequest* other) { - if (other == this) return; - ABSL_DCHECK(GetArena() == other->GetArena()); - InternalSwap(other); - } - - // implements Message ---------------------------------------------- - - EventRequest* New(::google::protobuf::Arena* arena = nullptr) const PROTOBUF_FINAL { - return ::google::protobuf::Message::DefaultConstruct(arena); - } - using ::google::protobuf::Message::CopyFrom; - void CopyFrom(const EventRequest& from); - using ::google::protobuf::Message::MergeFrom; - void MergeFrom(const EventRequest& from) { EventRequest::MergeImpl(*this, from); } - - private: - static void MergeImpl( - ::google::protobuf::MessageLite& to_msg, - const ::google::protobuf::MessageLite& from_msg); - - public: - bool IsInitialized() const { - return true; - } - ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; - #if defined(PROTOBUF_CUSTOM_VTABLE) - private: - static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); - static ::uint8_t* _InternalSerialize( - const MessageLite& msg, ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream); - - public: - ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const { - return _InternalSerialize(*this, target, stream); - } - #else // PROTOBUF_CUSTOM_VTABLE - ::size_t ByteSizeLong() const final; - ::uint8_t* _InternalSerialize( - ::uint8_t* target, - ::google::protobuf::io::EpsCopyOutputStream* stream) const final; - #endif // PROTOBUF_CUSTOM_VTABLE - int GetCachedSize() const { return _impl_._cached_size_.Get(); } - - private: - void SharedCtor(::google::protobuf::Arena* arena); - void SharedDtor(); - void InternalSwap(EventRequest* other); - private: - friend class ::google::protobuf::internal::AnyMetadata; - static ::absl::string_view FullMessageName() { return "satori.api.EventRequest"; } - - protected: - explicit EventRequest(::google::protobuf::Arena* arena); - EventRequest(::google::protobuf::Arena* arena, const EventRequest& from); - EventRequest(::google::protobuf::Arena* arena, EventRequest&& from) noexcept - : EventRequest(arena) { - *this = ::std::move(from); - } - const ::google::protobuf::Message::ClassData* GetClassData() const PROTOBUF_FINAL; - static const ::google::protobuf::Message::ClassDataFull _class_data_; - - public: - ::google::protobuf::Metadata GetMetadata() const; - // nested types ---------------------------------------------------- - - // accessors ------------------------------------------------------- - enum : int { - kEventsFieldNumber = 1, - }; - // repeated .satori.api.Event events = 1; - int events_size() const; - private: - int _internal_events_size() const; - - public: - void clear_events() ; - ::satori::api::Event* mutable_events(int index); - ::google::protobuf::RepeatedPtrField<::satori::api::Event>* mutable_events(); - - private: - const ::google::protobuf::RepeatedPtrField<::satori::api::Event>& _internal_events() const; - ::google::protobuf::RepeatedPtrField<::satori::api::Event>* _internal_mutable_events(); - public: - const ::satori::api::Event& events(int index) const; - ::satori::api::Event* add_events(); - const ::google::protobuf::RepeatedPtrField<::satori::api::Event>& events() const; - // @@protoc_insertion_point(class_scope:satori.api.EventRequest) - private: - class _Internal; - friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable< - 0, 1, 1, - 0, 2> - _table_; - - static constexpr const void* _raw_default_instance_ = - &_EventRequest_default_instance_; - - friend class ::google::protobuf::MessageLite; - friend class ::google::protobuf::Arena; - template - friend class ::google::protobuf::Arena::InternalHelper; - using InternalArenaConstructable_ = void; - using DestructorSkippable_ = void; - struct Impl_ { - inline explicit constexpr Impl_( - ::google::protobuf::internal::ConstantInitialized) noexcept; - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena); - inline explicit Impl_(::google::protobuf::internal::InternalVisibility visibility, - ::google::protobuf::Arena* arena, const Impl_& from, - const EventRequest& from_msg); - ::google::protobuf::RepeatedPtrField< ::satori::api::Event > events_; - mutable ::google::protobuf::internal::CachedSize _cached_size_; - PROTOBUF_TSAN_DECLARE_MEMBER - }; - union { Impl_ _impl_; }; - friend struct ::TableStruct_satori_2eproto; -}; - -// =================================================================== - - - - -// =================================================================== - - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif // __GNUC__ -// ------------------------------------------------------------------- - -// AuthenticateLogoutRequest - -// string token = 1; -inline void AuthenticateLogoutRequest::clear_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.ClearToEmpty(); -} -inline const std::string& AuthenticateLogoutRequest::token() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.AuthenticateLogoutRequest.token) - return _internal_token(); -} -template -inline PROTOBUF_ALWAYS_INLINE void AuthenticateLogoutRequest::set_token(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.AuthenticateLogoutRequest.token) -} -inline std::string* AuthenticateLogoutRequest::mutable_token() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_token(); - // @@protoc_insertion_point(field_mutable:satori.api.AuthenticateLogoutRequest.token) - return _s; -} -inline const std::string& AuthenticateLogoutRequest::_internal_token() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.token_.Get(); -} -inline void AuthenticateLogoutRequest::_internal_set_token(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.Set(value, GetArena()); -} -inline std::string* AuthenticateLogoutRequest::_internal_mutable_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.token_.Mutable( GetArena()); -} -inline std::string* AuthenticateLogoutRequest::release_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.AuthenticateLogoutRequest.token) - return _impl_.token_.Release(); -} -inline void AuthenticateLogoutRequest::set_allocated_token(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.token_.IsDefault()) { - _impl_.token_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.AuthenticateLogoutRequest.token) -} - -// string refresh_token = 2; -inline void AuthenticateLogoutRequest::clear_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.ClearToEmpty(); -} -inline const std::string& AuthenticateLogoutRequest::refresh_token() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.AuthenticateLogoutRequest.refresh_token) - return _internal_refresh_token(); -} -template -inline PROTOBUF_ALWAYS_INLINE void AuthenticateLogoutRequest::set_refresh_token(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.AuthenticateLogoutRequest.refresh_token) -} -inline std::string* AuthenticateLogoutRequest::mutable_refresh_token() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_refresh_token(); - // @@protoc_insertion_point(field_mutable:satori.api.AuthenticateLogoutRequest.refresh_token) - return _s; -} -inline const std::string& AuthenticateLogoutRequest::_internal_refresh_token() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.refresh_token_.Get(); -} -inline void AuthenticateLogoutRequest::_internal_set_refresh_token(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(value, GetArena()); -} -inline std::string* AuthenticateLogoutRequest::_internal_mutable_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.refresh_token_.Mutable( GetArena()); -} -inline std::string* AuthenticateLogoutRequest::release_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.AuthenticateLogoutRequest.refresh_token) - return _impl_.refresh_token_.Release(); -} -inline void AuthenticateLogoutRequest::set_allocated_refresh_token(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.refresh_token_.IsDefault()) { - _impl_.refresh_token_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.AuthenticateLogoutRequest.refresh_token) -} - -// ------------------------------------------------------------------- - -// AuthenticateRefreshRequest - -// string refresh_token = 1; -inline void AuthenticateRefreshRequest::clear_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.ClearToEmpty(); -} -inline const std::string& AuthenticateRefreshRequest::refresh_token() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.AuthenticateRefreshRequest.refresh_token) - return _internal_refresh_token(); -} -template -inline PROTOBUF_ALWAYS_INLINE void AuthenticateRefreshRequest::set_refresh_token(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.AuthenticateRefreshRequest.refresh_token) -} -inline std::string* AuthenticateRefreshRequest::mutable_refresh_token() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_refresh_token(); - // @@protoc_insertion_point(field_mutable:satori.api.AuthenticateRefreshRequest.refresh_token) - return _s; -} -inline const std::string& AuthenticateRefreshRequest::_internal_refresh_token() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.refresh_token_.Get(); -} -inline void AuthenticateRefreshRequest::_internal_set_refresh_token(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(value, GetArena()); -} -inline std::string* AuthenticateRefreshRequest::_internal_mutable_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.refresh_token_.Mutable( GetArena()); -} -inline std::string* AuthenticateRefreshRequest::release_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.AuthenticateRefreshRequest.refresh_token) - return _impl_.refresh_token_.Release(); -} -inline void AuthenticateRefreshRequest::set_allocated_refresh_token(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.refresh_token_.IsDefault()) { - _impl_.refresh_token_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.AuthenticateRefreshRequest.refresh_token) -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// AuthenticateRequest - -// string id = 1; -inline void AuthenticateRequest::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& AuthenticateRequest::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.AuthenticateRequest.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void AuthenticateRequest::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.AuthenticateRequest.id) -} -inline std::string* AuthenticateRequest::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.AuthenticateRequest.id) - return _s; -} -inline const std::string& AuthenticateRequest::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void AuthenticateRequest::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* AuthenticateRequest::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* AuthenticateRequest::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.AuthenticateRequest.id) - return _impl_.id_.Release(); -} -inline void AuthenticateRequest::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.AuthenticateRequest.id) -} - -// map default = 2; -inline int AuthenticateRequest::_internal_default__size() const { - return _internal_default_().size(); -} -inline int AuthenticateRequest::default__size() const { - return _internal_default__size(); -} -inline void AuthenticateRequest::clear_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.default__.Clear(); -} -inline const ::google::protobuf::Map& AuthenticateRequest::_internal_default_() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.default__.GetMap(); -} -inline const ::google::protobuf::Map& AuthenticateRequest::default_() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.AuthenticateRequest.default) - return _internal_default_(); -} -inline ::google::protobuf::Map* AuthenticateRequest::_internal_mutable_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.default__.MutableMap(); -} -inline ::google::protobuf::Map* AuthenticateRequest::mutable_default_() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.AuthenticateRequest.default) - return _internal_mutable_default_(); -} - -// map custom = 3; -inline int AuthenticateRequest::_internal_custom_size() const { - return _internal_custom().size(); -} -inline int AuthenticateRequest::custom_size() const { - return _internal_custom_size(); -} -inline void AuthenticateRequest::clear_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.custom_.Clear(); -} -inline const ::google::protobuf::Map& AuthenticateRequest::_internal_custom() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.custom_.GetMap(); -} -inline const ::google::protobuf::Map& AuthenticateRequest::custom() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.AuthenticateRequest.custom) - return _internal_custom(); -} -inline ::google::protobuf::Map* AuthenticateRequest::_internal_mutable_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.custom_.MutableMap(); -} -inline ::google::protobuf::Map* AuthenticateRequest::mutable_custom() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.AuthenticateRequest.custom) - return _internal_mutable_custom(); -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// Event - -// string name = 1; -inline void Event::clear_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.ClearToEmpty(); -} -inline const std::string& Event::name() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Event.name) - return _internal_name(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Event::set_name(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Event.name) -} -inline std::string* Event::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_name(); - // @@protoc_insertion_point(field_mutable:satori.api.Event.name) - return _s; -} -inline const std::string& Event::_internal_name() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.name_.Get(); -} -inline void Event::_internal_set_name(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(value, GetArena()); -} -inline std::string* Event::_internal_mutable_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.name_.Mutable( GetArena()); -} -inline std::string* Event::release_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Event.name) - return _impl_.name_.Release(); -} -inline void Event::set_allocated_name(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.name_.IsDefault()) { - _impl_.name_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Event.name) -} - -// string id = 2; -inline void Event::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& Event::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Event.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Event::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Event.id) -} -inline std::string* Event::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.Event.id) - return _s; -} -inline const std::string& Event::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void Event::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* Event::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* Event::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Event.id) - return _impl_.id_.Release(); -} -inline void Event::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Event.id) -} - -// map metadata = 3; -inline int Event::_internal_metadata_size() const { - return _internal_metadata().size(); -} -inline int Event::metadata_size() const { - return _internal_metadata_size(); -} -inline void Event::clear_metadata() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.metadata_.Clear(); -} -inline const ::google::protobuf::Map& Event::_internal_metadata() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.metadata_.GetMap(); -} -inline const ::google::protobuf::Map& Event::metadata() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.Event.metadata) - return _internal_metadata(); -} -inline ::google::protobuf::Map* Event::_internal_mutable_metadata() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.metadata_.MutableMap(); -} -inline ::google::protobuf::Map* Event::mutable_metadata() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.Event.metadata) - return _internal_mutable_metadata(); -} - -// string value = 4; -inline void Event::clear_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.ClearToEmpty(); -} -inline const std::string& Event::value() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Event.value) - return _internal_value(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Event::set_value(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Event.value) -} -inline std::string* Event::mutable_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_value(); - // @@protoc_insertion_point(field_mutable:satori.api.Event.value) - return _s; -} -inline const std::string& Event::_internal_value() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.value_.Get(); -} -inline void Event::_internal_set_value(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(value, GetArena()); -} -inline std::string* Event::_internal_mutable_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.value_.Mutable( GetArena()); -} -inline std::string* Event::release_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Event.value) - return _impl_.value_.Release(); -} -inline void Event::set_allocated_value(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.value_.IsDefault()) { - _impl_.value_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Event.value) -} - -// .google.protobuf.Timestamp timestamp = 5; -inline bool Event::has_timestamp() const { - bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; - PROTOBUF_ASSUME(!value || _impl_.timestamp_ != nullptr); - return value; -} -inline const ::google::protobuf::Timestamp& Event::_internal_timestamp() const { - ::google::protobuf::internal::TSanRead(&_impl_); - const ::google::protobuf::Timestamp* p = _impl_.timestamp_; - return p != nullptr ? *p : reinterpret_cast(::google::protobuf::_Timestamp_default_instance_); -} -inline const ::google::protobuf::Timestamp& Event::timestamp() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Event.timestamp) - return _internal_timestamp(); -} -inline void Event::unsafe_arena_set_allocated_timestamp(::google::protobuf::Timestamp* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (GetArena() == nullptr) { - delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.timestamp_); - } - _impl_.timestamp_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); - if (value != nullptr) { - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - // @@protoc_insertion_point(field_unsafe_arena_set_allocated:satori.api.Event.timestamp) -} -inline ::google::protobuf::Timestamp* Event::release_timestamp() { - ::google::protobuf::internal::TSanWrite(&_impl_); - - _impl_._has_bits_[0] &= ~0x00000001u; - ::google::protobuf::Timestamp* released = _impl_.timestamp_; - _impl_.timestamp_ = nullptr; -#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE - auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - if (GetArena() == nullptr) { - delete old; - } -#else // PROTOBUF_FORCE_COPY_IN_RELEASE - if (GetArena() != nullptr) { - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - } -#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE - return released; -} -inline ::google::protobuf::Timestamp* Event::unsafe_arena_release_timestamp() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Event.timestamp) - - _impl_._has_bits_[0] &= ~0x00000001u; - ::google::protobuf::Timestamp* temp = _impl_.timestamp_; - _impl_.timestamp_ = nullptr; - return temp; -} -inline ::google::protobuf::Timestamp* Event::_internal_mutable_timestamp() { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (_impl_.timestamp_ == nullptr) { - auto* p = ::google::protobuf::Message::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); - _impl_.timestamp_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); - } - return _impl_.timestamp_; -} -inline ::google::protobuf::Timestamp* Event::mutable_timestamp() ABSL_ATTRIBUTE_LIFETIME_BOUND { - _impl_._has_bits_[0] |= 0x00000001u; - ::google::protobuf::Timestamp* _msg = _internal_mutable_timestamp(); - // @@protoc_insertion_point(field_mutable:satori.api.Event.timestamp) - return _msg; -} -inline void Event::set_allocated_timestamp(::google::protobuf::Timestamp* value) { - ::google::protobuf::Arena* message_arena = GetArena(); - ::google::protobuf::internal::TSanWrite(&_impl_); - if (message_arena == nullptr) { - delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.timestamp_); - } - - if (value != nullptr) { - ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::MessageLite*>(value)->GetArena(); - if (message_arena != submessage_arena) { - value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); - } - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - - _impl_.timestamp_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); - // @@protoc_insertion_point(field_set_allocated:satori.api.Event.timestamp) -} - -// ------------------------------------------------------------------- - -// EventRequest - -// repeated .satori.api.Event events = 1; -inline int EventRequest::_internal_events_size() const { - return _internal_events().size(); -} -inline int EventRequest::events_size() const { - return _internal_events_size(); -} -inline void EventRequest::clear_events() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.events_.Clear(); -} -inline ::satori::api::Event* EventRequest::mutable_events(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.EventRequest.events) - return _internal_mutable_events()->Mutable(index); -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Event>* EventRequest::mutable_events() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.EventRequest.events) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_events(); -} -inline const ::satori::api::Event& EventRequest::events(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.EventRequest.events) - return _internal_events().Get(index); -} -inline ::satori::api::Event* EventRequest::add_events() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::satori::api::Event* _add = _internal_mutable_events()->Add(); - // @@protoc_insertion_point(field_add:satori.api.EventRequest.events) - return _add; -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Event>& EventRequest::events() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.EventRequest.events) - return _internal_events(); -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Event>& -EventRequest::_internal_events() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.events_; -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Event>* -EventRequest::_internal_mutable_events() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.events_; -} - -// ------------------------------------------------------------------- - -// Experiment - -// string name = 1; -inline void Experiment::clear_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.ClearToEmpty(); -} -inline const std::string& Experiment::name() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Experiment.name) - return _internal_name(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Experiment::set_name(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Experiment.name) -} -inline std::string* Experiment::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_name(); - // @@protoc_insertion_point(field_mutable:satori.api.Experiment.name) - return _s; -} -inline const std::string& Experiment::_internal_name() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.name_.Get(); -} -inline void Experiment::_internal_set_name(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(value, GetArena()); -} -inline std::string* Experiment::_internal_mutable_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.name_.Mutable( GetArena()); -} -inline std::string* Experiment::release_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Experiment.name) - return _impl_.name_.Release(); -} -inline void Experiment::set_allocated_name(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.name_.IsDefault()) { - _impl_.name_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Experiment.name) -} - -// string value = 2; -inline void Experiment::clear_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.ClearToEmpty(); -} -inline const std::string& Experiment::value() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Experiment.value) - return _internal_value(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Experiment::set_value(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Experiment.value) -} -inline std::string* Experiment::mutable_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_value(); - // @@protoc_insertion_point(field_mutable:satori.api.Experiment.value) - return _s; -} -inline const std::string& Experiment::_internal_value() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.value_.Get(); -} -inline void Experiment::_internal_set_value(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(value, GetArena()); -} -inline std::string* Experiment::_internal_mutable_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.value_.Mutable( GetArena()); -} -inline std::string* Experiment::release_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Experiment.value) - return _impl_.value_.Release(); -} -inline void Experiment::set_allocated_value(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.value_.IsDefault()) { - _impl_.value_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Experiment.value) -} - -// ------------------------------------------------------------------- - -// ExperimentList - -// repeated .satori.api.Experiment experiments = 1; -inline int ExperimentList::_internal_experiments_size() const { - return _internal_experiments().size(); -} -inline int ExperimentList::experiments_size() const { - return _internal_experiments_size(); -} -inline void ExperimentList::clear_experiments() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.experiments_.Clear(); -} -inline ::satori::api::Experiment* ExperimentList::mutable_experiments(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.ExperimentList.experiments) - return _internal_mutable_experiments()->Mutable(index); -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>* ExperimentList::mutable_experiments() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.ExperimentList.experiments) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_experiments(); -} -inline const ::satori::api::Experiment& ExperimentList::experiments(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.ExperimentList.experiments) - return _internal_experiments().Get(index); -} -inline ::satori::api::Experiment* ExperimentList::add_experiments() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::satori::api::Experiment* _add = _internal_mutable_experiments()->Add(); - // @@protoc_insertion_point(field_add:satori.api.ExperimentList.experiments) - return _add; -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>& ExperimentList::experiments() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.ExperimentList.experiments) - return _internal_experiments(); -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>& -ExperimentList::_internal_experiments() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.experiments_; -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Experiment>* -ExperimentList::_internal_mutable_experiments() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.experiments_; -} - -// ------------------------------------------------------------------- - -// Flag - -// string name = 1; -inline void Flag::clear_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.ClearToEmpty(); -} -inline const std::string& Flag::name() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Flag.name) - return _internal_name(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Flag::set_name(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Flag.name) -} -inline std::string* Flag::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_name(); - // @@protoc_insertion_point(field_mutable:satori.api.Flag.name) - return _s; -} -inline const std::string& Flag::_internal_name() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.name_.Get(); -} -inline void Flag::_internal_set_name(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(value, GetArena()); -} -inline std::string* Flag::_internal_mutable_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.name_.Mutable( GetArena()); -} -inline std::string* Flag::release_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Flag.name) - return _impl_.name_.Release(); -} -inline void Flag::set_allocated_name(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.name_.IsDefault()) { - _impl_.name_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Flag.name) -} - -// string value = 2; -inline void Flag::clear_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.ClearToEmpty(); -} -inline const std::string& Flag::value() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Flag.value) - return _internal_value(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Flag::set_value(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Flag.value) -} -inline std::string* Flag::mutable_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_value(); - // @@protoc_insertion_point(field_mutable:satori.api.Flag.value) - return _s; -} -inline const std::string& Flag::_internal_value() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.value_.Get(); -} -inline void Flag::_internal_set_value(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(value, GetArena()); -} -inline std::string* Flag::_internal_mutable_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.value_.Mutable( GetArena()); -} -inline std::string* Flag::release_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Flag.value) - return _impl_.value_.Release(); -} -inline void Flag::set_allocated_value(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.value_.IsDefault()) { - _impl_.value_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Flag.value) -} - -// bool condition_changed = 3; -inline void Flag::clear_condition_changed() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.condition_changed_ = false; -} -inline bool Flag::condition_changed() const { - // @@protoc_insertion_point(field_get:satori.api.Flag.condition_changed) - return _internal_condition_changed(); -} -inline void Flag::set_condition_changed(bool value) { - _internal_set_condition_changed(value); - // @@protoc_insertion_point(field_set:satori.api.Flag.condition_changed) -} -inline bool Flag::_internal_condition_changed() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.condition_changed_; -} -inline void Flag::_internal_set_condition_changed(bool value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.condition_changed_ = value; -} - -// ------------------------------------------------------------------- - -// FlagList - -// repeated .satori.api.Flag flags = 1; -inline int FlagList::_internal_flags_size() const { - return _internal_flags().size(); -} -inline int FlagList::flags_size() const { - return _internal_flags_size(); -} -inline void FlagList::clear_flags() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.flags_.Clear(); -} -inline ::satori::api::Flag* FlagList::mutable_flags(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.FlagList.flags) - return _internal_mutable_flags()->Mutable(index); -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Flag>* FlagList::mutable_flags() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.FlagList.flags) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_flags(); -} -inline const ::satori::api::Flag& FlagList::flags(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.FlagList.flags) - return _internal_flags().Get(index); -} -inline ::satori::api::Flag* FlagList::add_flags() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::satori::api::Flag* _add = _internal_mutable_flags()->Add(); - // @@protoc_insertion_point(field_add:satori.api.FlagList.flags) - return _add; -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Flag>& FlagList::flags() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.FlagList.flags) - return _internal_flags(); -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Flag>& -FlagList::_internal_flags() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.flags_; -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Flag>* -FlagList::_internal_mutable_flags() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.flags_; -} - -// ------------------------------------------------------------------- - -// GetExperimentsRequest - -// repeated string names = 1; -inline int GetExperimentsRequest::_internal_names_size() const { - return _internal_names().size(); -} -inline int GetExperimentsRequest::names_size() const { - return _internal_names_size(); -} -inline void GetExperimentsRequest::clear_names() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.names_.Clear(); -} -inline std::string* GetExperimentsRequest::add_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - std::string* _s = _internal_mutable_names()->Add(); - // @@protoc_insertion_point(field_add_mutable:satori.api.GetExperimentsRequest.names) - return _s; -} -inline const std::string& GetExperimentsRequest::names(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetExperimentsRequest.names) - return _internal_names().Get(index); -} -inline std::string* GetExperimentsRequest::mutable_names(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.GetExperimentsRequest.names) - return _internal_mutable_names()->Mutable(index); -} -template -inline void GetExperimentsRequest::set_names(int index, Arg_&& value, Args_... args) { - ::google::protobuf::internal::AssignToString( - *_internal_mutable_names()->Mutable(index), - std::forward(value), args... ); - // @@protoc_insertion_point(field_set:satori.api.GetExperimentsRequest.names) -} -template -inline void GetExperimentsRequest::add_names(Arg_&& value, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::google::protobuf::internal::AddToRepeatedPtrField(*_internal_mutable_names(), - std::forward(value), - args... ); - // @@protoc_insertion_point(field_add:satori.api.GetExperimentsRequest.names) -} -inline const ::google::protobuf::RepeatedPtrField& -GetExperimentsRequest::names() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.GetExperimentsRequest.names) - return _internal_names(); -} -inline ::google::protobuf::RepeatedPtrField* -GetExperimentsRequest::mutable_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.GetExperimentsRequest.names) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_names(); -} -inline const ::google::protobuf::RepeatedPtrField& -GetExperimentsRequest::_internal_names() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.names_; -} -inline ::google::protobuf::RepeatedPtrField* -GetExperimentsRequest::_internal_mutable_names() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.names_; -} - -// ------------------------------------------------------------------- - -// GetFlagsRequest - -// repeated string names = 1; -inline int GetFlagsRequest::_internal_names_size() const { - return _internal_names().size(); -} -inline int GetFlagsRequest::names_size() const { - return _internal_names_size(); -} -inline void GetFlagsRequest::clear_names() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.names_.Clear(); -} -inline std::string* GetFlagsRequest::add_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - std::string* _s = _internal_mutable_names()->Add(); - // @@protoc_insertion_point(field_add_mutable:satori.api.GetFlagsRequest.names) - return _s; -} -inline const std::string& GetFlagsRequest::names(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetFlagsRequest.names) - return _internal_names().Get(index); -} -inline std::string* GetFlagsRequest::mutable_names(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.GetFlagsRequest.names) - return _internal_mutable_names()->Mutable(index); -} -template -inline void GetFlagsRequest::set_names(int index, Arg_&& value, Args_... args) { - ::google::protobuf::internal::AssignToString( - *_internal_mutable_names()->Mutable(index), - std::forward(value), args... ); - // @@protoc_insertion_point(field_set:satori.api.GetFlagsRequest.names) -} -template -inline void GetFlagsRequest::add_names(Arg_&& value, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::google::protobuf::internal::AddToRepeatedPtrField(*_internal_mutable_names(), - std::forward(value), - args... ); - // @@protoc_insertion_point(field_add:satori.api.GetFlagsRequest.names) -} -inline const ::google::protobuf::RepeatedPtrField& -GetFlagsRequest::names() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.GetFlagsRequest.names) - return _internal_names(); -} -inline ::google::protobuf::RepeatedPtrField* -GetFlagsRequest::mutable_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.GetFlagsRequest.names) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_names(); -} -inline const ::google::protobuf::RepeatedPtrField& -GetFlagsRequest::_internal_names() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.names_; -} -inline ::google::protobuf::RepeatedPtrField* -GetFlagsRequest::_internal_mutable_names() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.names_; -} - -// ------------------------------------------------------------------- - -// GetLiveEventsRequest - -// repeated string names = 1; -inline int GetLiveEventsRequest::_internal_names_size() const { - return _internal_names().size(); -} -inline int GetLiveEventsRequest::names_size() const { - return _internal_names_size(); -} -inline void GetLiveEventsRequest::clear_names() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.names_.Clear(); -} -inline std::string* GetLiveEventsRequest::add_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - std::string* _s = _internal_mutable_names()->Add(); - // @@protoc_insertion_point(field_add_mutable:satori.api.GetLiveEventsRequest.names) - return _s; -} -inline const std::string& GetLiveEventsRequest::names(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetLiveEventsRequest.names) - return _internal_names().Get(index); -} -inline std::string* GetLiveEventsRequest::mutable_names(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.GetLiveEventsRequest.names) - return _internal_mutable_names()->Mutable(index); -} -template -inline void GetLiveEventsRequest::set_names(int index, Arg_&& value, Args_... args) { - ::google::protobuf::internal::AssignToString( - *_internal_mutable_names()->Mutable(index), - std::forward(value), args... ); - // @@protoc_insertion_point(field_set:satori.api.GetLiveEventsRequest.names) -} -template -inline void GetLiveEventsRequest::add_names(Arg_&& value, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::google::protobuf::internal::AddToRepeatedPtrField(*_internal_mutable_names(), - std::forward(value), - args... ); - // @@protoc_insertion_point(field_add:satori.api.GetLiveEventsRequest.names) -} -inline const ::google::protobuf::RepeatedPtrField& -GetLiveEventsRequest::names() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.GetLiveEventsRequest.names) - return _internal_names(); -} -inline ::google::protobuf::RepeatedPtrField* -GetLiveEventsRequest::mutable_names() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.GetLiveEventsRequest.names) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_names(); -} -inline const ::google::protobuf::RepeatedPtrField& -GetLiveEventsRequest::_internal_names() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.names_; -} -inline ::google::protobuf::RepeatedPtrField* -GetLiveEventsRequest::_internal_mutable_names() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.names_; -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// IdentifyRequest - -// string id = 1; -inline void IdentifyRequest::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& IdentifyRequest::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.IdentifyRequest.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void IdentifyRequest::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.IdentifyRequest.id) -} -inline std::string* IdentifyRequest::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.IdentifyRequest.id) - return _s; -} -inline const std::string& IdentifyRequest::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void IdentifyRequest::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* IdentifyRequest::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* IdentifyRequest::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.IdentifyRequest.id) - return _impl_.id_.Release(); -} -inline void IdentifyRequest::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.IdentifyRequest.id) -} - -// map default = 2; -inline int IdentifyRequest::_internal_default__size() const { - return _internal_default_().size(); -} -inline int IdentifyRequest::default__size() const { - return _internal_default__size(); -} -inline void IdentifyRequest::clear_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.default__.Clear(); -} -inline const ::google::protobuf::Map& IdentifyRequest::_internal_default_() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.default__.GetMap(); -} -inline const ::google::protobuf::Map& IdentifyRequest::default_() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.IdentifyRequest.default) - return _internal_default_(); -} -inline ::google::protobuf::Map* IdentifyRequest::_internal_mutable_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.default__.MutableMap(); -} -inline ::google::protobuf::Map* IdentifyRequest::mutable_default_() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.IdentifyRequest.default) - return _internal_mutable_default_(); -} - -// map custom = 3; -inline int IdentifyRequest::_internal_custom_size() const { - return _internal_custom().size(); -} -inline int IdentifyRequest::custom_size() const { - return _internal_custom_size(); -} -inline void IdentifyRequest::clear_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.custom_.Clear(); -} -inline const ::google::protobuf::Map& IdentifyRequest::_internal_custom() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.custom_.GetMap(); -} -inline const ::google::protobuf::Map& IdentifyRequest::custom() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.IdentifyRequest.custom) - return _internal_custom(); -} -inline ::google::protobuf::Map* IdentifyRequest::_internal_mutable_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.custom_.MutableMap(); -} -inline ::google::protobuf::Map* IdentifyRequest::mutable_custom() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.IdentifyRequest.custom) - return _internal_mutable_custom(); -} - -// ------------------------------------------------------------------- - -// LiveEvent - -// string name = 1; -inline void LiveEvent::clear_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.ClearToEmpty(); -} -inline const std::string& LiveEvent::name() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.name) - return _internal_name(); -} -template -inline PROTOBUF_ALWAYS_INLINE void LiveEvent::set_name(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.name) -} -inline std::string* LiveEvent::mutable_name() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_name(); - // @@protoc_insertion_point(field_mutable:satori.api.LiveEvent.name) - return _s; -} -inline const std::string& LiveEvent::_internal_name() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.name_.Get(); -} -inline void LiveEvent::_internal_set_name(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.Set(value, GetArena()); -} -inline std::string* LiveEvent::_internal_mutable_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.name_.Mutable( GetArena()); -} -inline std::string* LiveEvent::release_name() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.LiveEvent.name) - return _impl_.name_.Release(); -} -inline void LiveEvent::set_allocated_name(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.name_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.name_.IsDefault()) { - _impl_.name_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.LiveEvent.name) -} - -// string description = 2; -inline void LiveEvent::clear_description() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.description_.ClearToEmpty(); -} -inline const std::string& LiveEvent::description() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.description) - return _internal_description(); -} -template -inline PROTOBUF_ALWAYS_INLINE void LiveEvent::set_description(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.description_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.description) -} -inline std::string* LiveEvent::mutable_description() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_description(); - // @@protoc_insertion_point(field_mutable:satori.api.LiveEvent.description) - return _s; -} -inline const std::string& LiveEvent::_internal_description() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.description_.Get(); -} -inline void LiveEvent::_internal_set_description(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.description_.Set(value, GetArena()); -} -inline std::string* LiveEvent::_internal_mutable_description() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.description_.Mutable( GetArena()); -} -inline std::string* LiveEvent::release_description() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.LiveEvent.description) - return _impl_.description_.Release(); -} -inline void LiveEvent::set_allocated_description(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.description_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.description_.IsDefault()) { - _impl_.description_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.LiveEvent.description) -} - -// string value = 3; -inline void LiveEvent::clear_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.ClearToEmpty(); -} -inline const std::string& LiveEvent::value() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.value) - return _internal_value(); -} -template -inline PROTOBUF_ALWAYS_INLINE void LiveEvent::set_value(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.value) -} -inline std::string* LiveEvent::mutable_value() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_value(); - // @@protoc_insertion_point(field_mutable:satori.api.LiveEvent.value) - return _s; -} -inline const std::string& LiveEvent::_internal_value() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.value_.Get(); -} -inline void LiveEvent::_internal_set_value(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.Set(value, GetArena()); -} -inline std::string* LiveEvent::_internal_mutable_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.value_.Mutable( GetArena()); -} -inline std::string* LiveEvent::release_value() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.LiveEvent.value) - return _impl_.value_.Release(); -} -inline void LiveEvent::set_allocated_value(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.value_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.value_.IsDefault()) { - _impl_.value_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.LiveEvent.value) -} - -// int64 active_start_time_sec = 4; -inline void LiveEvent::clear_active_start_time_sec() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.active_start_time_sec_ = ::int64_t{0}; -} -inline ::int64_t LiveEvent::active_start_time_sec() const { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.active_start_time_sec) - return _internal_active_start_time_sec(); -} -inline void LiveEvent::set_active_start_time_sec(::int64_t value) { - _internal_set_active_start_time_sec(value); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.active_start_time_sec) -} -inline ::int64_t LiveEvent::_internal_active_start_time_sec() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.active_start_time_sec_; -} -inline void LiveEvent::_internal_set_active_start_time_sec(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.active_start_time_sec_ = value; -} - -// int64 active_end_time_sec = 5; -inline void LiveEvent::clear_active_end_time_sec() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.active_end_time_sec_ = ::int64_t{0}; -} -inline ::int64_t LiveEvent::active_end_time_sec() const { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.active_end_time_sec) - return _internal_active_end_time_sec(); -} -inline void LiveEvent::set_active_end_time_sec(::int64_t value) { - _internal_set_active_end_time_sec(value); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.active_end_time_sec) -} -inline ::int64_t LiveEvent::_internal_active_end_time_sec() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.active_end_time_sec_; -} -inline void LiveEvent::_internal_set_active_end_time_sec(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.active_end_time_sec_ = value; -} - -// string id = 6; -inline void LiveEvent::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& LiveEvent::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void LiveEvent::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.id) -} -inline std::string* LiveEvent::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.LiveEvent.id) - return _s; -} -inline const std::string& LiveEvent::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void LiveEvent::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* LiveEvent::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* LiveEvent::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.LiveEvent.id) - return _impl_.id_.Release(); -} -inline void LiveEvent::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.LiveEvent.id) -} - -// int64 start_time_sec = 7; -inline void LiveEvent::clear_start_time_sec() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.start_time_sec_ = ::int64_t{0}; -} -inline ::int64_t LiveEvent::start_time_sec() const { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.start_time_sec) - return _internal_start_time_sec(); -} -inline void LiveEvent::set_start_time_sec(::int64_t value) { - _internal_set_start_time_sec(value); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.start_time_sec) -} -inline ::int64_t LiveEvent::_internal_start_time_sec() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.start_time_sec_; -} -inline void LiveEvent::_internal_set_start_time_sec(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.start_time_sec_ = value; -} - -// int64 end_time_sec = 8; -inline void LiveEvent::clear_end_time_sec() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.end_time_sec_ = ::int64_t{0}; -} -inline ::int64_t LiveEvent::end_time_sec() const { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.end_time_sec) - return _internal_end_time_sec(); -} -inline void LiveEvent::set_end_time_sec(::int64_t value) { - _internal_set_end_time_sec(value); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.end_time_sec) -} -inline ::int64_t LiveEvent::_internal_end_time_sec() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.end_time_sec_; -} -inline void LiveEvent::_internal_set_end_time_sec(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.end_time_sec_ = value; -} - -// int64 duration_sec = 9; -inline void LiveEvent::clear_duration_sec() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.duration_sec_ = ::int64_t{0}; -} -inline ::int64_t LiveEvent::duration_sec() const { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.duration_sec) - return _internal_duration_sec(); -} -inline void LiveEvent::set_duration_sec(::int64_t value) { - _internal_set_duration_sec(value); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.duration_sec) -} -inline ::int64_t LiveEvent::_internal_duration_sec() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.duration_sec_; -} -inline void LiveEvent::_internal_set_duration_sec(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.duration_sec_ = value; -} - -// string reset_cron = 10; -inline void LiveEvent::clear_reset_cron() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.reset_cron_.ClearToEmpty(); -} -inline const std::string& LiveEvent::reset_cron() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEvent.reset_cron) - return _internal_reset_cron(); -} -template -inline PROTOBUF_ALWAYS_INLINE void LiveEvent::set_reset_cron(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.reset_cron_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.LiveEvent.reset_cron) -} -inline std::string* LiveEvent::mutable_reset_cron() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_reset_cron(); - // @@protoc_insertion_point(field_mutable:satori.api.LiveEvent.reset_cron) - return _s; -} -inline const std::string& LiveEvent::_internal_reset_cron() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.reset_cron_.Get(); -} -inline void LiveEvent::_internal_set_reset_cron(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.reset_cron_.Set(value, GetArena()); -} -inline std::string* LiveEvent::_internal_mutable_reset_cron() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.reset_cron_.Mutable( GetArena()); -} -inline std::string* LiveEvent::release_reset_cron() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.LiveEvent.reset_cron) - return _impl_.reset_cron_.Release(); -} -inline void LiveEvent::set_allocated_reset_cron(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.reset_cron_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.reset_cron_.IsDefault()) { - _impl_.reset_cron_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.LiveEvent.reset_cron) -} - -// ------------------------------------------------------------------- - -// LiveEventList - -// repeated .satori.api.LiveEvent live_events = 1; -inline int LiveEventList::_internal_live_events_size() const { - return _internal_live_events().size(); -} -inline int LiveEventList::live_events_size() const { - return _internal_live_events_size(); -} -inline void LiveEventList::clear_live_events() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.live_events_.Clear(); -} -inline ::satori::api::LiveEvent* LiveEventList::mutable_live_events(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.LiveEventList.live_events) - return _internal_mutable_live_events()->Mutable(index); -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>* LiveEventList::mutable_live_events() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.LiveEventList.live_events) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_live_events(); -} -inline const ::satori::api::LiveEvent& LiveEventList::live_events(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.LiveEventList.live_events) - return _internal_live_events().Get(index); -} -inline ::satori::api::LiveEvent* LiveEventList::add_live_events() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::satori::api::LiveEvent* _add = _internal_mutable_live_events()->Add(); - // @@protoc_insertion_point(field_add:satori.api.LiveEventList.live_events) - return _add; -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>& LiveEventList::live_events() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.LiveEventList.live_events) - return _internal_live_events(); -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>& -LiveEventList::_internal_live_events() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.live_events_; -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::LiveEvent>* -LiveEventList::_internal_mutable_live_events() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.live_events_; -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// Properties - -// map default = 1; -inline int Properties::_internal_default__size() const { - return _internal_default_().size(); -} -inline int Properties::default__size() const { - return _internal_default__size(); -} -inline void Properties::clear_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.default__.Clear(); -} -inline const ::google::protobuf::Map& Properties::_internal_default_() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.default__.GetMap(); -} -inline const ::google::protobuf::Map& Properties::default_() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.Properties.default) - return _internal_default_(); -} -inline ::google::protobuf::Map* Properties::_internal_mutable_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.default__.MutableMap(); -} -inline ::google::protobuf::Map* Properties::mutable_default_() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.Properties.default) - return _internal_mutable_default_(); -} - -// map computed = 2; -inline int Properties::_internal_computed_size() const { - return _internal_computed().size(); -} -inline int Properties::computed_size() const { - return _internal_computed_size(); -} -inline void Properties::clear_computed() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.computed_.Clear(); -} -inline const ::google::protobuf::Map& Properties::_internal_computed() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.computed_.GetMap(); -} -inline const ::google::protobuf::Map& Properties::computed() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.Properties.computed) - return _internal_computed(); -} -inline ::google::protobuf::Map* Properties::_internal_mutable_computed() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.computed_.MutableMap(); -} -inline ::google::protobuf::Map* Properties::mutable_computed() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.Properties.computed) - return _internal_mutable_computed(); -} - -// map custom = 3; -inline int Properties::_internal_custom_size() const { - return _internal_custom().size(); -} -inline int Properties::custom_size() const { - return _internal_custom_size(); -} -inline void Properties::clear_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.custom_.Clear(); -} -inline const ::google::protobuf::Map& Properties::_internal_custom() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.custom_.GetMap(); -} -inline const ::google::protobuf::Map& Properties::custom() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.Properties.custom) - return _internal_custom(); -} -inline ::google::protobuf::Map* Properties::_internal_mutable_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.custom_.MutableMap(); -} -inline ::google::protobuf::Map* Properties::mutable_custom() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.Properties.custom) - return _internal_mutable_custom(); -} - -// ------------------------------------------------------------------- - -// Session - -// string token = 1; -inline void Session::clear_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.ClearToEmpty(); -} -inline const std::string& Session::token() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Session.token) - return _internal_token(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Session::set_token(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Session.token) -} -inline std::string* Session::mutable_token() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_token(); - // @@protoc_insertion_point(field_mutable:satori.api.Session.token) - return _s; -} -inline const std::string& Session::_internal_token() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.token_.Get(); -} -inline void Session::_internal_set_token(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.Set(value, GetArena()); -} -inline std::string* Session::_internal_mutable_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.token_.Mutable( GetArena()); -} -inline std::string* Session::release_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Session.token) - return _impl_.token_.Release(); -} -inline void Session::set_allocated_token(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.token_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.token_.IsDefault()) { - _impl_.token_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Session.token) -} - -// string refresh_token = 2; -inline void Session::clear_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.ClearToEmpty(); -} -inline const std::string& Session::refresh_token() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Session.refresh_token) - return _internal_refresh_token(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Session::set_refresh_token(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Session.refresh_token) -} -inline std::string* Session::mutable_refresh_token() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_refresh_token(); - // @@protoc_insertion_point(field_mutable:satori.api.Session.refresh_token) - return _s; -} -inline const std::string& Session::_internal_refresh_token() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.refresh_token_.Get(); -} -inline void Session::_internal_set_refresh_token(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.Set(value, GetArena()); -} -inline std::string* Session::_internal_mutable_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.refresh_token_.Mutable( GetArena()); -} -inline std::string* Session::release_refresh_token() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Session.refresh_token) - return _impl_.refresh_token_.Release(); -} -inline void Session::set_allocated_refresh_token(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.refresh_token_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.refresh_token_.IsDefault()) { - _impl_.refresh_token_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Session.refresh_token) -} - -// .satori.api.Properties properties = 3; -inline bool Session::has_properties() const { - bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; - PROTOBUF_ASSUME(!value || _impl_.properties_ != nullptr); - return value; -} -inline void Session::clear_properties() { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (_impl_.properties_ != nullptr) _impl_.properties_->Clear(); - _impl_._has_bits_[0] &= ~0x00000001u; -} -inline const ::satori::api::Properties& Session::_internal_properties() const { - ::google::protobuf::internal::TSanRead(&_impl_); - const ::satori::api::Properties* p = _impl_.properties_; - return p != nullptr ? *p : reinterpret_cast(::satori::api::_Properties_default_instance_); -} -inline const ::satori::api::Properties& Session::properties() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Session.properties) - return _internal_properties(); -} -inline void Session::unsafe_arena_set_allocated_properties(::satori::api::Properties* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (GetArena() == nullptr) { - delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.properties_); - } - _impl_.properties_ = reinterpret_cast<::satori::api::Properties*>(value); - if (value != nullptr) { - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - // @@protoc_insertion_point(field_unsafe_arena_set_allocated:satori.api.Session.properties) -} -inline ::satori::api::Properties* Session::release_properties() { - ::google::protobuf::internal::TSanWrite(&_impl_); - - _impl_._has_bits_[0] &= ~0x00000001u; - ::satori::api::Properties* released = _impl_.properties_; - _impl_.properties_ = nullptr; -#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE - auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - if (GetArena() == nullptr) { - delete old; - } -#else // PROTOBUF_FORCE_COPY_IN_RELEASE - if (GetArena() != nullptr) { - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - } -#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE - return released; -} -inline ::satori::api::Properties* Session::unsafe_arena_release_properties() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Session.properties) - - _impl_._has_bits_[0] &= ~0x00000001u; - ::satori::api::Properties* temp = _impl_.properties_; - _impl_.properties_ = nullptr; - return temp; -} -inline ::satori::api::Properties* Session::_internal_mutable_properties() { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (_impl_.properties_ == nullptr) { - auto* p = ::google::protobuf::Message::DefaultConstruct<::satori::api::Properties>(GetArena()); - _impl_.properties_ = reinterpret_cast<::satori::api::Properties*>(p); - } - return _impl_.properties_; -} -inline ::satori::api::Properties* Session::mutable_properties() ABSL_ATTRIBUTE_LIFETIME_BOUND { - _impl_._has_bits_[0] |= 0x00000001u; - ::satori::api::Properties* _msg = _internal_mutable_properties(); - // @@protoc_insertion_point(field_mutable:satori.api.Session.properties) - return _msg; -} -inline void Session::set_allocated_properties(::satori::api::Properties* value) { - ::google::protobuf::Arena* message_arena = GetArena(); - ::google::protobuf::internal::TSanWrite(&_impl_); - if (message_arena == nullptr) { - delete (_impl_.properties_); - } - - if (value != nullptr) { - ::google::protobuf::Arena* submessage_arena = (value)->GetArena(); - if (message_arena != submessage_arena) { - value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); - } - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - - _impl_.properties_ = reinterpret_cast<::satori::api::Properties*>(value); - // @@protoc_insertion_point(field_set_allocated:satori.api.Session.properties) -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// UpdatePropertiesRequest - -// map default = 1; -inline int UpdatePropertiesRequest::_internal_default__size() const { - return _internal_default_().size(); -} -inline int UpdatePropertiesRequest::default__size() const { - return _internal_default__size(); -} -inline void UpdatePropertiesRequest::clear_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.default__.Clear(); -} -inline const ::google::protobuf::Map& UpdatePropertiesRequest::_internal_default_() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.default__.GetMap(); -} -inline const ::google::protobuf::Map& UpdatePropertiesRequest::default_() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.UpdatePropertiesRequest.default) - return _internal_default_(); -} -inline ::google::protobuf::Map* UpdatePropertiesRequest::_internal_mutable_default_() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.default__.MutableMap(); -} -inline ::google::protobuf::Map* UpdatePropertiesRequest::mutable_default_() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.UpdatePropertiesRequest.default) - return _internal_mutable_default_(); -} - -// map custom = 2; -inline int UpdatePropertiesRequest::_internal_custom_size() const { - return _internal_custom().size(); -} -inline int UpdatePropertiesRequest::custom_size() const { - return _internal_custom_size(); -} -inline void UpdatePropertiesRequest::clear_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.custom_.Clear(); -} -inline const ::google::protobuf::Map& UpdatePropertiesRequest::_internal_custom() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.custom_.GetMap(); -} -inline const ::google::protobuf::Map& UpdatePropertiesRequest::custom() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.UpdatePropertiesRequest.custom) - return _internal_custom(); -} -inline ::google::protobuf::Map* UpdatePropertiesRequest::_internal_mutable_custom() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.custom_.MutableMap(); -} -inline ::google::protobuf::Map* UpdatePropertiesRequest::mutable_custom() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.UpdatePropertiesRequest.custom) - return _internal_mutable_custom(); -} - -// .google.protobuf.BoolValue recompute = 3; -inline bool UpdatePropertiesRequest::has_recompute() const { - bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; - PROTOBUF_ASSUME(!value || _impl_.recompute_ != nullptr); - return value; -} -inline const ::google::protobuf::BoolValue& UpdatePropertiesRequest::_internal_recompute() const { - ::google::protobuf::internal::TSanRead(&_impl_); - const ::google::protobuf::BoolValue* p = _impl_.recompute_; - return p != nullptr ? *p : reinterpret_cast(::google::protobuf::_BoolValue_default_instance_); -} -inline const ::google::protobuf::BoolValue& UpdatePropertiesRequest::recompute() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.UpdatePropertiesRequest.recompute) - return _internal_recompute(); -} -inline void UpdatePropertiesRequest::unsafe_arena_set_allocated_recompute(::google::protobuf::BoolValue* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (GetArena() == nullptr) { - delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.recompute_); - } - _impl_.recompute_ = reinterpret_cast<::google::protobuf::BoolValue*>(value); - if (value != nullptr) { - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - // @@protoc_insertion_point(field_unsafe_arena_set_allocated:satori.api.UpdatePropertiesRequest.recompute) -} -inline ::google::protobuf::BoolValue* UpdatePropertiesRequest::release_recompute() { - ::google::protobuf::internal::TSanWrite(&_impl_); - - _impl_._has_bits_[0] &= ~0x00000001u; - ::google::protobuf::BoolValue* released = _impl_.recompute_; - _impl_.recompute_ = nullptr; -#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE - auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - if (GetArena() == nullptr) { - delete old; - } -#else // PROTOBUF_FORCE_COPY_IN_RELEASE - if (GetArena() != nullptr) { - released = ::google::protobuf::internal::DuplicateIfNonNull(released); - } -#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE - return released; -} -inline ::google::protobuf::BoolValue* UpdatePropertiesRequest::unsafe_arena_release_recompute() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.UpdatePropertiesRequest.recompute) - - _impl_._has_bits_[0] &= ~0x00000001u; - ::google::protobuf::BoolValue* temp = _impl_.recompute_; - _impl_.recompute_ = nullptr; - return temp; -} -inline ::google::protobuf::BoolValue* UpdatePropertiesRequest::_internal_mutable_recompute() { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (_impl_.recompute_ == nullptr) { - auto* p = ::google::protobuf::Message::DefaultConstruct<::google::protobuf::BoolValue>(GetArena()); - _impl_.recompute_ = reinterpret_cast<::google::protobuf::BoolValue*>(p); - } - return _impl_.recompute_; -} -inline ::google::protobuf::BoolValue* UpdatePropertiesRequest::mutable_recompute() ABSL_ATTRIBUTE_LIFETIME_BOUND { - _impl_._has_bits_[0] |= 0x00000001u; - ::google::protobuf::BoolValue* _msg = _internal_mutable_recompute(); - // @@protoc_insertion_point(field_mutable:satori.api.UpdatePropertiesRequest.recompute) - return _msg; -} -inline void UpdatePropertiesRequest::set_allocated_recompute(::google::protobuf::BoolValue* value) { - ::google::protobuf::Arena* message_arena = GetArena(); - ::google::protobuf::internal::TSanWrite(&_impl_); - if (message_arena == nullptr) { - delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.recompute_); - } - - if (value != nullptr) { - ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::MessageLite*>(value)->GetArena(); - if (message_arena != submessage_arena) { - value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); - } - _impl_._has_bits_[0] |= 0x00000001u; - } else { - _impl_._has_bits_[0] &= ~0x00000001u; - } - - _impl_.recompute_ = reinterpret_cast<::google::protobuf::BoolValue*>(value); - // @@protoc_insertion_point(field_set_allocated:satori.api.UpdatePropertiesRequest.recompute) -} - -// ------------------------------------------------------------------- - -// GetMessageListRequest - -// int32 limit = 1; -inline void GetMessageListRequest::clear_limit() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.limit_ = 0; -} -inline ::int32_t GetMessageListRequest::limit() const { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListRequest.limit) - return _internal_limit(); -} -inline void GetMessageListRequest::set_limit(::int32_t value) { - _internal_set_limit(value); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListRequest.limit) -} -inline ::int32_t GetMessageListRequest::_internal_limit() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.limit_; -} -inline void GetMessageListRequest::_internal_set_limit(::int32_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.limit_ = value; -} - -// bool forward = 2; -inline void GetMessageListRequest::clear_forward() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.forward_ = false; -} -inline bool GetMessageListRequest::forward() const { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListRequest.forward) - return _internal_forward(); -} -inline void GetMessageListRequest::set_forward(bool value) { - _internal_set_forward(value); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListRequest.forward) -} -inline bool GetMessageListRequest::_internal_forward() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.forward_; -} -inline void GetMessageListRequest::_internal_set_forward(bool value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.forward_ = value; -} - -// string cursor = 3; -inline void GetMessageListRequest::clear_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cursor_.ClearToEmpty(); -} -inline const std::string& GetMessageListRequest::cursor() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListRequest.cursor) - return _internal_cursor(); -} -template -inline PROTOBUF_ALWAYS_INLINE void GetMessageListRequest::set_cursor(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cursor_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListRequest.cursor) -} -inline std::string* GetMessageListRequest::mutable_cursor() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_cursor(); - // @@protoc_insertion_point(field_mutable:satori.api.GetMessageListRequest.cursor) - return _s; -} -inline const std::string& GetMessageListRequest::_internal_cursor() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.cursor_.Get(); -} -inline void GetMessageListRequest::_internal_set_cursor(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cursor_.Set(value, GetArena()); -} -inline std::string* GetMessageListRequest::_internal_mutable_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.cursor_.Mutable( GetArena()); -} -inline std::string* GetMessageListRequest::release_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.GetMessageListRequest.cursor) - return _impl_.cursor_.Release(); -} -inline void GetMessageListRequest::set_allocated_cursor(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cursor_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.cursor_.IsDefault()) { - _impl_.cursor_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.GetMessageListRequest.cursor) -} - -// ------------------------------------------------------------------- - -// GetMessageListResponse - -// repeated .satori.api.Message messages = 1; -inline int GetMessageListResponse::_internal_messages_size() const { - return _internal_messages().size(); -} -inline int GetMessageListResponse::messages_size() const { - return _internal_messages_size(); -} -inline void GetMessageListResponse::clear_messages() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.messages_.Clear(); -} -inline ::satori::api::Message* GetMessageListResponse::mutable_messages(int index) - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable:satori.api.GetMessageListResponse.messages) - return _internal_mutable_messages()->Mutable(index); -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Message>* GetMessageListResponse::mutable_messages() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_list:satori.api.GetMessageListResponse.messages) - ::google::protobuf::internal::TSanWrite(&_impl_); - return _internal_mutable_messages(); -} -inline const ::satori::api::Message& GetMessageListResponse::messages(int index) const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListResponse.messages) - return _internal_messages().Get(index); -} -inline ::satori::api::Message* GetMessageListResponse::add_messages() ABSL_ATTRIBUTE_LIFETIME_BOUND { - ::google::protobuf::internal::TSanWrite(&_impl_); - ::satori::api::Message* _add = _internal_mutable_messages()->Add(); - // @@protoc_insertion_point(field_add:satori.api.GetMessageListResponse.messages) - return _add; -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Message>& GetMessageListResponse::messages() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_list:satori.api.GetMessageListResponse.messages) - return _internal_messages(); -} -inline const ::google::protobuf::RepeatedPtrField<::satori::api::Message>& -GetMessageListResponse::_internal_messages() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.messages_; -} -inline ::google::protobuf::RepeatedPtrField<::satori::api::Message>* -GetMessageListResponse::_internal_mutable_messages() { - ::google::protobuf::internal::TSanRead(&_impl_); - return &_impl_.messages_; -} - -// string next_cursor = 2; -inline void GetMessageListResponse::clear_next_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.next_cursor_.ClearToEmpty(); -} -inline const std::string& GetMessageListResponse::next_cursor() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListResponse.next_cursor) - return _internal_next_cursor(); -} -template -inline PROTOBUF_ALWAYS_INLINE void GetMessageListResponse::set_next_cursor(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.next_cursor_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListResponse.next_cursor) -} -inline std::string* GetMessageListResponse::mutable_next_cursor() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_next_cursor(); - // @@protoc_insertion_point(field_mutable:satori.api.GetMessageListResponse.next_cursor) - return _s; -} -inline const std::string& GetMessageListResponse::_internal_next_cursor() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.next_cursor_.Get(); -} -inline void GetMessageListResponse::_internal_set_next_cursor(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.next_cursor_.Set(value, GetArena()); -} -inline std::string* GetMessageListResponse::_internal_mutable_next_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.next_cursor_.Mutable( GetArena()); -} -inline std::string* GetMessageListResponse::release_next_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.GetMessageListResponse.next_cursor) - return _impl_.next_cursor_.Release(); -} -inline void GetMessageListResponse::set_allocated_next_cursor(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.next_cursor_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.next_cursor_.IsDefault()) { - _impl_.next_cursor_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.GetMessageListResponse.next_cursor) -} - -// string prev_cursor = 3; -inline void GetMessageListResponse::clear_prev_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.prev_cursor_.ClearToEmpty(); -} -inline const std::string& GetMessageListResponse::prev_cursor() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListResponse.prev_cursor) - return _internal_prev_cursor(); -} -template -inline PROTOBUF_ALWAYS_INLINE void GetMessageListResponse::set_prev_cursor(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.prev_cursor_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListResponse.prev_cursor) -} -inline std::string* GetMessageListResponse::mutable_prev_cursor() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_prev_cursor(); - // @@protoc_insertion_point(field_mutable:satori.api.GetMessageListResponse.prev_cursor) - return _s; -} -inline const std::string& GetMessageListResponse::_internal_prev_cursor() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.prev_cursor_.Get(); -} -inline void GetMessageListResponse::_internal_set_prev_cursor(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.prev_cursor_.Set(value, GetArena()); -} -inline std::string* GetMessageListResponse::_internal_mutable_prev_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.prev_cursor_.Mutable( GetArena()); -} -inline std::string* GetMessageListResponse::release_prev_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.GetMessageListResponse.prev_cursor) - return _impl_.prev_cursor_.Release(); -} -inline void GetMessageListResponse::set_allocated_prev_cursor(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.prev_cursor_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.prev_cursor_.IsDefault()) { - _impl_.prev_cursor_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.GetMessageListResponse.prev_cursor) -} - -// string cacheable_cursor = 4; -inline void GetMessageListResponse::clear_cacheable_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cacheable_cursor_.ClearToEmpty(); -} -inline const std::string& GetMessageListResponse::cacheable_cursor() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.GetMessageListResponse.cacheable_cursor) - return _internal_cacheable_cursor(); -} -template -inline PROTOBUF_ALWAYS_INLINE void GetMessageListResponse::set_cacheable_cursor(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cacheable_cursor_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.GetMessageListResponse.cacheable_cursor) -} -inline std::string* GetMessageListResponse::mutable_cacheable_cursor() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_cacheable_cursor(); - // @@protoc_insertion_point(field_mutable:satori.api.GetMessageListResponse.cacheable_cursor) - return _s; -} -inline const std::string& GetMessageListResponse::_internal_cacheable_cursor() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.cacheable_cursor_.Get(); -} -inline void GetMessageListResponse::_internal_set_cacheable_cursor(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cacheable_cursor_.Set(value, GetArena()); -} -inline std::string* GetMessageListResponse::_internal_mutable_cacheable_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.cacheable_cursor_.Mutable( GetArena()); -} -inline std::string* GetMessageListResponse::release_cacheable_cursor() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.GetMessageListResponse.cacheable_cursor) - return _impl_.cacheable_cursor_.Release(); -} -inline void GetMessageListResponse::set_allocated_cacheable_cursor(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.cacheable_cursor_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.cacheable_cursor_.IsDefault()) { - _impl_.cacheable_cursor_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.GetMessageListResponse.cacheable_cursor) -} - -// ------------------------------------------------------------------- - -// ------------------------------------------------------------------- - -// Message - -// string schedule_id = 1; -inline void Message::clear_schedule_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.schedule_id_.ClearToEmpty(); -} -inline const std::string& Message::schedule_id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Message.schedule_id) - return _internal_schedule_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Message::set_schedule_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.schedule_id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Message.schedule_id) -} -inline std::string* Message::mutable_schedule_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_schedule_id(); - // @@protoc_insertion_point(field_mutable:satori.api.Message.schedule_id) - return _s; -} -inline const std::string& Message::_internal_schedule_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.schedule_id_.Get(); -} -inline void Message::_internal_set_schedule_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.schedule_id_.Set(value, GetArena()); -} -inline std::string* Message::_internal_mutable_schedule_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.schedule_id_.Mutable( GetArena()); -} -inline std::string* Message::release_schedule_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Message.schedule_id) - return _impl_.schedule_id_.Release(); -} -inline void Message::set_allocated_schedule_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.schedule_id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.schedule_id_.IsDefault()) { - _impl_.schedule_id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Message.schedule_id) -} - -// int64 send_time = 2; -inline void Message::clear_send_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.send_time_ = ::int64_t{0}; -} -inline ::int64_t Message::send_time() const { - // @@protoc_insertion_point(field_get:satori.api.Message.send_time) - return _internal_send_time(); -} -inline void Message::set_send_time(::int64_t value) { - _internal_set_send_time(value); - // @@protoc_insertion_point(field_set:satori.api.Message.send_time) -} -inline ::int64_t Message::_internal_send_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.send_time_; -} -inline void Message::_internal_set_send_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.send_time_ = value; -} - -// map metadata = 3; -inline int Message::_internal_metadata_size() const { - return _internal_metadata().size(); -} -inline int Message::metadata_size() const { - return _internal_metadata_size(); -} -inline void Message::clear_metadata() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.metadata_.Clear(); -} -inline const ::google::protobuf::Map& Message::_internal_metadata() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.metadata_.GetMap(); -} -inline const ::google::protobuf::Map& Message::metadata() const ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_map:satori.api.Message.metadata) - return _internal_metadata(); -} -inline ::google::protobuf::Map* Message::_internal_mutable_metadata() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.metadata_.MutableMap(); -} -inline ::google::protobuf::Map* Message::mutable_metadata() ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_mutable_map:satori.api.Message.metadata) - return _internal_mutable_metadata(); -} - -// int64 create_time = 4; -inline void Message::clear_create_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.create_time_ = ::int64_t{0}; -} -inline ::int64_t Message::create_time() const { - // @@protoc_insertion_point(field_get:satori.api.Message.create_time) - return _internal_create_time(); -} -inline void Message::set_create_time(::int64_t value) { - _internal_set_create_time(value); - // @@protoc_insertion_point(field_set:satori.api.Message.create_time) -} -inline ::int64_t Message::_internal_create_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.create_time_; -} -inline void Message::_internal_set_create_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.create_time_ = value; -} - -// int64 update_time = 5; -inline void Message::clear_update_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.update_time_ = ::int64_t{0}; -} -inline ::int64_t Message::update_time() const { - // @@protoc_insertion_point(field_get:satori.api.Message.update_time) - return _internal_update_time(); -} -inline void Message::set_update_time(::int64_t value) { - _internal_set_update_time(value); - // @@protoc_insertion_point(field_set:satori.api.Message.update_time) -} -inline ::int64_t Message::_internal_update_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.update_time_; -} -inline void Message::_internal_set_update_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.update_time_ = value; -} - -// int64 read_time = 6; -inline void Message::clear_read_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.read_time_ = ::int64_t{0}; -} -inline ::int64_t Message::read_time() const { - // @@protoc_insertion_point(field_get:satori.api.Message.read_time) - return _internal_read_time(); -} -inline void Message::set_read_time(::int64_t value) { - _internal_set_read_time(value); - // @@protoc_insertion_point(field_set:satori.api.Message.read_time) -} -inline ::int64_t Message::_internal_read_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.read_time_; -} -inline void Message::_internal_set_read_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.read_time_ = value; -} - -// int64 consume_time = 7; -inline void Message::clear_consume_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.consume_time_ = ::int64_t{0}; -} -inline ::int64_t Message::consume_time() const { - // @@protoc_insertion_point(field_get:satori.api.Message.consume_time) - return _internal_consume_time(); -} -inline void Message::set_consume_time(::int64_t value) { - _internal_set_consume_time(value); - // @@protoc_insertion_point(field_set:satori.api.Message.consume_time) -} -inline ::int64_t Message::_internal_consume_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.consume_time_; -} -inline void Message::_internal_set_consume_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.consume_time_ = value; -} - -// string text = 8; -inline void Message::clear_text() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.text_.ClearToEmpty(); -} -inline const std::string& Message::text() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Message.text) - return _internal_text(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Message::set_text(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.text_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Message.text) -} -inline std::string* Message::mutable_text() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_text(); - // @@protoc_insertion_point(field_mutable:satori.api.Message.text) - return _s; -} -inline const std::string& Message::_internal_text() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.text_.Get(); -} -inline void Message::_internal_set_text(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.text_.Set(value, GetArena()); -} -inline std::string* Message::_internal_mutable_text() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.text_.Mutable( GetArena()); -} -inline std::string* Message::release_text() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Message.text) - return _impl_.text_.Release(); -} -inline void Message::set_allocated_text(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.text_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.text_.IsDefault()) { - _impl_.text_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Message.text) -} - -// string id = 9; -inline void Message::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& Message::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Message.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Message::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Message.id) -} -inline std::string* Message::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.Message.id) - return _s; -} -inline const std::string& Message::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void Message::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* Message::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* Message::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Message.id) - return _impl_.id_.Release(); -} -inline void Message::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Message.id) -} - -// string title = 10; -inline void Message::clear_title() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.title_.ClearToEmpty(); -} -inline const std::string& Message::title() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Message.title) - return _internal_title(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Message::set_title(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.title_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Message.title) -} -inline std::string* Message::mutable_title() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_title(); - // @@protoc_insertion_point(field_mutable:satori.api.Message.title) - return _s; -} -inline const std::string& Message::_internal_title() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.title_.Get(); -} -inline void Message::_internal_set_title(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.title_.Set(value, GetArena()); -} -inline std::string* Message::_internal_mutable_title() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.title_.Mutable( GetArena()); -} -inline std::string* Message::release_title() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Message.title) - return _impl_.title_.Release(); -} -inline void Message::set_allocated_title(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.title_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.title_.IsDefault()) { - _impl_.title_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Message.title) -} - -// string image_url = 11; -inline void Message::clear_image_url() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.image_url_.ClearToEmpty(); -} -inline const std::string& Message::image_url() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.Message.image_url) - return _internal_image_url(); -} -template -inline PROTOBUF_ALWAYS_INLINE void Message::set_image_url(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.image_url_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.Message.image_url) -} -inline std::string* Message::mutable_image_url() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_image_url(); - // @@protoc_insertion_point(field_mutable:satori.api.Message.image_url) - return _s; -} -inline const std::string& Message::_internal_image_url() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.image_url_.Get(); -} -inline void Message::_internal_set_image_url(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.image_url_.Set(value, GetArena()); -} -inline std::string* Message::_internal_mutable_image_url() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.image_url_.Mutable( GetArena()); -} -inline std::string* Message::release_image_url() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.Message.image_url) - return _impl_.image_url_.Release(); -} -inline void Message::set_allocated_image_url(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.image_url_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.image_url_.IsDefault()) { - _impl_.image_url_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.Message.image_url) -} - -// ------------------------------------------------------------------- - -// UpdateMessageRequest - -// string id = 1; -inline void UpdateMessageRequest::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& UpdateMessageRequest::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.UpdateMessageRequest.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void UpdateMessageRequest::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.UpdateMessageRequest.id) -} -inline std::string* UpdateMessageRequest::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.UpdateMessageRequest.id) - return _s; -} -inline const std::string& UpdateMessageRequest::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void UpdateMessageRequest::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* UpdateMessageRequest::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* UpdateMessageRequest::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.UpdateMessageRequest.id) - return _impl_.id_.Release(); -} -inline void UpdateMessageRequest::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.UpdateMessageRequest.id) -} - -// int64 read_time = 2; -inline void UpdateMessageRequest::clear_read_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.read_time_ = ::int64_t{0}; -} -inline ::int64_t UpdateMessageRequest::read_time() const { - // @@protoc_insertion_point(field_get:satori.api.UpdateMessageRequest.read_time) - return _internal_read_time(); -} -inline void UpdateMessageRequest::set_read_time(::int64_t value) { - _internal_set_read_time(value); - // @@protoc_insertion_point(field_set:satori.api.UpdateMessageRequest.read_time) -} -inline ::int64_t UpdateMessageRequest::_internal_read_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.read_time_; -} -inline void UpdateMessageRequest::_internal_set_read_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.read_time_ = value; -} - -// int64 consume_time = 3; -inline void UpdateMessageRequest::clear_consume_time() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.consume_time_ = ::int64_t{0}; -} -inline ::int64_t UpdateMessageRequest::consume_time() const { - // @@protoc_insertion_point(field_get:satori.api.UpdateMessageRequest.consume_time) - return _internal_consume_time(); -} -inline void UpdateMessageRequest::set_consume_time(::int64_t value) { - _internal_set_consume_time(value); - // @@protoc_insertion_point(field_set:satori.api.UpdateMessageRequest.consume_time) -} -inline ::int64_t UpdateMessageRequest::_internal_consume_time() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.consume_time_; -} -inline void UpdateMessageRequest::_internal_set_consume_time(::int64_t value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.consume_time_ = value; -} - -// ------------------------------------------------------------------- - -// DeleteMessageRequest - -// string id = 1; -inline void DeleteMessageRequest::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); -} -inline const std::string& DeleteMessageRequest::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:satori.api.DeleteMessageRequest.id) - return _internal_id(); -} -template -inline PROTOBUF_ALWAYS_INLINE void DeleteMessageRequest::set_id(Arg_&& arg, - Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:satori.api.DeleteMessageRequest.id) -} -inline std::string* DeleteMessageRequest::mutable_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:satori.api.DeleteMessageRequest.id) - return _s; -} -inline const std::string& DeleteMessageRequest::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void DeleteMessageRequest::_internal_set_id(const std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline std::string* DeleteMessageRequest::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline std::string* DeleteMessageRequest::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:satori.api.DeleteMessageRequest.id) - return _impl_.id_.Release(); -} -inline void DeleteMessageRequest::set_allocated_id(std::string* value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.SetAllocated(value, GetArena()); - #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING - if (_impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING - // @@protoc_insertion_point(field_set_allocated:satori.api.DeleteMessageRequest.id) -} - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif // __GNUC__ - -// @@protoc_insertion_point(namespace_scope) -} // namespace api -} // namespace satori - - -// @@protoc_insertion_point(global_scope) - -#include "google/protobuf/port_undef.inc" - -#endif // GOOGLE_PROTOBUF_INCLUDED_satori_2eproto_2epb_2eh diff --git a/Satori/Source/SatoriCore/SatoriCore.build.cs b/Satori/Source/SatoriCore/SatoriCore.build.cs deleted file mode 100644 index f56bf1b33..000000000 --- a/Satori/Source/SatoriCore/SatoriCore.build.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using UnrealBuildTool; - -public class SatoriCore : ModuleRules -{ - public SatoriCore(ReadOnlyTargetRules target) : base(target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - // Platform -> (buildLib, runtimeLib) - var libs = new Dictionary>() - { - {UnrealTargetPlatform.Linux, Tuple.Create(Path.Combine("linux-x64", "libsatori-sdk.so"), Path.Combine("linux-x64", "libsatori-sdk.so"))}, - }; - - if (target.Platform == UnrealTargetPlatform.Win64) - { - string configurationDirectory = null; - string arch = null; - - if (IsX64Arch()) - { - arch = "win-x64"; - } - else - { - arch = "win-arm64"; - } - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - configurationDirectory = "Debug"; - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", "satori-sdk.pdb"), Path.Combine(ModuleDirectory, "libsatori", arch, configurationDirectory, "satori-sdk.pdb")); - } - else - { - configurationDirectory = "Release"; - } - - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libsatori", arch, configurationDirectory, "satori-sdk.lib")); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", "satori-sdk.dll"), Path.Combine(ModuleDirectory, "libsatori", arch, configurationDirectory, "satori-sdk.dll")); - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string dylibPath; - if (IsX64Arch()) - { - dylibPath = Path.Combine(ModuleDirectory, "libsatori", "macosx-x64", "libsatori-sdk.dylib"); - } - else - { - dylibPath = Path.Combine(ModuleDirectory, "libsatori", "macosx-arm64", "libsatori-sdk.dylib"); - } - - PublicDelayLoadDLLs.Add(dylibPath); - RuntimeDependencies.Add(dylibPath); - } - else if (Target.Platform == UnrealTargetPlatform.IOS) - { - var dylibPath = Path.Combine(ModuleDirectory, "libsatori", "ios-arm64", "libsatori-sdk.dylib"); - PublicDelayLoadDLLs.Add(dylibPath); - RuntimeDependencies.Add(dylibPath); - } - else if (Target.Platform == UnrealTargetPlatform.Android) - { - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libsatori", "armeabi-v7a", "libsatori-sdk.so")); - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libsatori", "arm64-v8a", "libsatori-sdk.so")); - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libsatori", "x86_64", "libsatori-sdk.so")); - - string relAPLPath = Utils.MakePathRelativeTo(Path.Combine(ModuleDirectory, "Satori_APL.xml"), Target.RelativeEnginePath); - AdditionalPropertiesForReceipt.Add("AndroidPlugin", relAPLPath); - } - else - { - if (!libs.ContainsKey(Target.Platform)) - { - throw new BuildException("Unsupported platform"); - } - - var libFiles = libs[Target.Platform]; - - PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "libsatori", libFiles.Item1)); - RuntimeDependencies.Add(Path.Combine("$(BinaryOutputDir)", Path.GetFileName(libFiles.Item2)), Path.Combine(ModuleDirectory, "libsatori", libFiles.Item2)); - } - - PrivateDependencyModuleNames.AddRange(new string[]{ "Core", "HTTP" }); - PublicDependencyModuleNames.AddRange(new string[]{ "WebSockets" }); - } - - private bool IsX64Arch() - { -#if UE_5_2_OR_LATER - return Target.Architecture == UnrealArch.X64; -#else - // empty string is the "default architecture" which is x64 - return Target.Architecture == "" || Target.Architecture.StartsWith("x86_64") || Target.Architecture.StartsWith("x64"); -#endif - } -} diff --git a/Satori/Source/SatoriCore/Satori_APL.xml b/Satori/Source/SatoriCore/Satori_APL.xml deleted file mode 100644 index 4379cfdfc..000000000 --- a/Satori/Source/SatoriCore/Satori_APL.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Satori/Source/SatoriCore/libsatori/android/arm64-v8a/libnakama-sdk.so b/Satori/Source/SatoriCore/libsatori/android/arm64-v8a/libnakama-sdk.so deleted file mode 100644 index 29988410f..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/android/arm64-v8a/libnakama-sdk.so and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/android/armeabi-v7a/libnakama-sdk.so b/Satori/Source/SatoriCore/libsatori/android/armeabi-v7a/libnakama-sdk.so deleted file mode 100644 index 6cd41eaec..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/android/armeabi-v7a/libnakama-sdk.so and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/android/x86_64/libnakama-sdk.so b/Satori/Source/SatoriCore/libsatori/android/x86_64/libnakama-sdk.so deleted file mode 100644 index ea7684040..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/android/x86_64/libnakama-sdk.so and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/ios-arm64/libnakama-sdk.dylib b/Satori/Source/SatoriCore/libsatori/ios-arm64/libnakama-sdk.dylib deleted file mode 100644 index 3bcce3980..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/ios-arm64/libnakama-sdk.dylib and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/linux-x64/libnakama-sdk.so b/Satori/Source/SatoriCore/libsatori/linux-x64/libnakama-sdk.so deleted file mode 100644 index ba48c15e1..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/linux-x64/libnakama-sdk.so and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/macosx-arm64/libnakama-sdk.dylib b/Satori/Source/SatoriCore/libsatori/macosx-arm64/libnakama-sdk.dylib deleted file mode 100644 index 19208fa2b..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/macosx-arm64/libnakama-sdk.dylib and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.dll b/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.dll deleted file mode 100644 index 8b029f60e..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.dll and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.lib b/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.lib deleted file mode 100644 index 74fe2c4a7..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.lib and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.pdb b/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.pdb deleted file mode 100644 index 2e2932fdd..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-arm64/Debug/nakama-sdk.pdb and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.dll b/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.dll deleted file mode 100644 index bc832e389..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.dll and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.lib b/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.lib deleted file mode 100644 index 74fe2c4a7..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-arm64/Release/nakama-sdk.lib and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.dll b/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.dll deleted file mode 100644 index 5e4fd6890..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.dll and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.lib b/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.lib deleted file mode 100644 index 33319d8f5..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.lib and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.pdb b/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.pdb deleted file mode 100644 index dc710dc7c..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-x64/Debug/nakama-sdk.pdb and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.dll b/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.dll deleted file mode 100644 index c3193fa2f..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.dll and /dev/null differ diff --git a/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.lib b/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.lib deleted file mode 100644 index 33319d8f5..000000000 Binary files a/Satori/Source/SatoriCore/libsatori/win-x64/Release/nakama-sdk.lib and /dev/null differ diff --git a/Satori/Source/SatoriUnreal/Private/SatoriClient.cpp b/Satori/Source/SatoriUnreal/Private/SatoriClient.cpp deleted file mode 100644 index c3e5ce8fe..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriClient.cpp +++ /dev/null @@ -1,1981 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriClient.h" -#include "SatoriUtils.h" -#include "GenericPlatform/GenericPlatformHttp.h" -#include "Interfaces/IHttpResponse.h" - -void USatoriClient::InitializeClient( - const FString& InHostname, - int32 InPort, - const FString& InServerKey, - bool bInUseSSL, - bool EnableDebug) -{ - Hostname = InHostname; - Port = InPort; - ServerKey = InServerKey; - bUseSSL = bInUseSSL; - bEnableDebug = EnableDebug; -} - -void USatoriClient::InitializeSystem( - const FString& InServerKey, - const FString& Host, - int32 InPort, - bool UseSSL, - bool EnableDebug) -{ - InitializeClient(Host, InPort, InServerKey, UseSSL, EnableDebug); - bIsActive = true; -} - -void USatoriClient::Disconnect() -{ - if (IsValidLowLevel()) - { - CancelAllRequests(); - } -} - -void USatoriClient::CancelAllRequests() -{ - if (!IsValidLowLevel()) - { - return; - } - - // Lock the mutex to protect access to ActiveRequests - FScopeLock Lock(&ActiveRequestsMutex); - - // Iterate over the active requests and cancel each one - for (const FHttpRequestPtr& Request : ActiveRequests) - { - Request->OnProcessRequestComplete().Unbind(); - Request->CancelRequest(); - } - - // Clear the ActiveRequests array - ActiveRequests.Empty(); -} - -void USatoriClient::Destroy() -{ - bIsActive = false; - ConditionalBeginDestroy(); -} - -void USatoriClient::SetTimeout(float InTimeout) -{ - Timeout = InTimeout; -} - -float USatoriClient::GetTimeout() -{ - return Timeout; -} - -void USatoriClient::BeginDestroy() -{ - UObject::BeginDestroy(); - bIsActive = false; -} - -USatoriClient* USatoriClient::CreateDefaultClient( - const FString& ServerKey, - const FString& Host, - int32 Port, - bool UseSSL, - bool EnableDebug) -{ - USatoriClient* NewClient = NewObject(); - NewClient->InitializeSystem(ServerKey, Host, Port, UseSSL, EnableDebug); - - if (EnableDebug) - { - USatoriLogger::EnableLogging(true); - USatoriLogger::SetLogLevel(ESatoriLogLevel::Debug); - } - - return NewClient; -} - -void USatoriClient::Authenticate( - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](USatoriSession* session) - { - if (!IsClientActive(this)) - return; - - Success.Broadcast(session); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - return; - - Error.Broadcast(error); - }; - - // A custom identifier must contain alphanumeric - // characters with dashesand be between 6 and 128 bytes. - - Authenticate(ID, DefaultProperties, CustomProperties, bNoSession, successCallback, errorCallback); -} - -void USatoriClient::Authenticate( - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/authenticate"); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - - ContentJson->SetStringField(TEXT("id"), ID); - if (bNoSession) - { - ContentJson->SetBoolField(TEXT("no_session"), bNoSession); - } - - if(!DefaultProperties.IsEmpty()) - { - TSharedPtr DefaultPropertiesJson = MakeShared(); - for (const TPair& Pair : DefaultProperties) - { - DefaultPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("default"), DefaultPropertiesJson); - } - - if (!CustomProperties.IsEmpty()) - { - TSharedPtr CustomPropertiesJson = MakeShared(); - for (const TPair& Pair : CustomProperties) - { - CustomPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("custom"), CustomPropertiesJson); - } - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::POST, TMultiMap(), ""); - - // Set the basic authorization header - FSatoriUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(USatoriSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::AuthenticateRefresh( - USatoriSession* Session, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](USatoriSession* UserSession) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(UserSession); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateRefresh(Session, successCallback, errorCallback); -} - -void USatoriClient::AuthenticateRefresh( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/authenticate/refresh"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - TSharedPtr ContentJson = MakeShareable(new FJsonObject); - ContentJson->SetStringField(TEXT("refresh_token"), Session->GetRefreshToken()); - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::POST, TMultiMap(), ""); - - // Set the basic authorization header - FSatoriUtils::SetBasicAuthorizationHeader(HttpRequest, ServerKey); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(USatoriSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::AuthenticateLogout( - USatoriSession* Session, - FOnAuthLogoutSent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - AuthenticateLogout(Session, successCallback, errorCallback); -} - -void USatoriClient::AuthenticateLogout( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/authenticate/logout"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - TSharedPtr ContentJson = MakeShareable(new FJsonObject); - ContentJson->SetStringField(TEXT("token"), Session->GetAuthToken()); - ContentJson->SetStringField(TEXT("refresh_token"), Session->GetRefreshToken()); - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::Identify( - USatoriSession* Session, - const FString& ID, - const TMap& defaultProperties, - const TMap& customProperties, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](USatoriSession* UserSession) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(UserSession); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - Identify(Session, ID, defaultProperties, customProperties, successCallback, errorCallback); -} - -void USatoriClient::Identify( - USatoriSession* Session, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/identify"); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - - ContentJson->SetStringField(TEXT("id"), ID); - - if(!DefaultProperties.IsEmpty()) - { - TSharedPtr DefaultPropertiesJson = MakeShared(); - for (const TPair& Pair : DefaultProperties) - { - DefaultPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("default"), DefaultPropertiesJson); - } - - if (!CustomProperties.IsEmpty()) - { - TSharedPtr CustomPropertiesJson = MakeShared(); - for (const TPair& Pair : CustomProperties) - { - CustomPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("custom"), CustomPropertiesJson); - } - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(USatoriSession::SetupSession(ResponseBody)); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::ListIdentityProperties( - USatoriSession* Session, - FOnGetProperties Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriProperties& Properties) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(Properties); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - ListIdentityProperties(Session, successCallback, errorCallback); -} - -void USatoriClient::ListIdentityProperties( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/properties"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriProperties Properties = FSatoriProperties(ResponseBody); - SuccessCallback(Properties); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::UpdateProperties( - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute, - FOnUpdatePropertiesSent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateProperties(Session, DefaultProperties, CustomProperties, bRecompute, successCallback, errorCallback); -} - -void USatoriClient::UpdateProperties( - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/properties"); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - - if (!DefaultProperties.IsEmpty()) - { - TSharedPtr DefaultPropertiesJson = MakeShared(); - for (const TPair& Pair : DefaultProperties) - { - DefaultPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("default"), DefaultPropertiesJson); - } - - if (!CustomProperties.IsEmpty()) - { - TSharedPtr CustomPropertiesJson = MakeShared(); - for (const TPair& Pair : CustomProperties) - { - CustomPropertiesJson->SetStringField(Pair.Key, Pair.Value); - } - ContentJson->SetObjectField(TEXT("custom"), CustomPropertiesJson); - } - - ContentJson->SetBoolField(TEXT("recompute"), bRecompute); - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::DeleteIdentity( - USatoriSession* Session, - FOnDeleteIdentitySent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteIdentity(Session, successCallback, errorCallback); -} - -void USatoriClient::DeleteIdentity( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/identity"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::DEL, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::PostEvent( - USatoriSession* Session, - const TArray& events, - FOnPostEventSent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - PostEvent(Session, events, successCallback, errorCallback); -} - -void USatoriClient::PostEvent( - USatoriSession* Session, - const TArray& Events, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/event"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the request content - TSharedPtr ContentJson = MakeShareable(new FJsonObject); - - // Setup the request body - TArray> EventsJson; - for (const FSatoriEvent& Event : Events) - { - TSharedPtr EventJson = MakeShareable(new FJsonObject()); - EventJson->SetStringField(TEXT("name"), Event.Name); - if (!Event.ID.IsEmpty()) - { - EventJson->SetStringField(TEXT("id"), Event.ID); - } - FSatoriUtils::AddVarsToJson(EventJson, Event.Metadata, TEXT("metadata")); - if (!Event.Value.IsEmpty()) - { - EventJson->SetStringField(TEXT("value"), Event.Value); - } - EventJson->SetStringField(TEXT("timestamp"), Event.Timestamp.ToIso8601());// google rpc requires RFC 3339, let's just hope Unreal's ISO 8601 keeps being copliant with it. - EventsJson.Add(MakeShareable(new FJsonValueObject(EventJson))); - } - - ContentJson->SetArrayField(TEXT("events"), EventsJson); - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::POST, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::GetExperiments( - USatoriSession* Session, - const TArray& Names, - FOnGetExperiments Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriExperimentList& Experiments) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(Experiments); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetExperiments(Session, Names, successCallback, errorCallback); -} - -void USatoriClient::GetExperiments( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/experiment"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - for (const FString& Name : Names) - { - QueryParams.Add(TEXT("names"), Name); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriExperimentList Experiments = FSatoriExperimentList(ResponseBody); - SuccessCallback(Experiments); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::GetFlags( - USatoriSession* Session, - const TArray& Names, - FOnGetFlags Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriFlagList& Flags) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(Flags); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetFlags(Session, Names, successCallback, errorCallback); -} - -void USatoriClient::GetFlags( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/flag"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - for (const FString& Name : Names) - { - QueryParams.Add(TEXT("names"), Name); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriFlagList Flags = FSatoriFlagList(ResponseBody); - SuccessCallback(Flags); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::GetFlagOverrides( - USatoriSession* Session, - const TArray& Names, - FOnGetFlagOverrides Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriFlagOverrideList& Flags) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(Flags); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetFlagOverrides(Session, Names, successCallback, errorCallback); -} - -void USatoriClient::GetFlagOverrides( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/flag/override"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - for (const FString& Name : Names) - { - QueryParams.Add(TEXT("names"), Name); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriFlagOverrideList FlagOverrides = FSatoriFlagOverrideList(ResponseBody); - SuccessCallback(FlagOverrides); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::GetLiveEvents( - USatoriSession* Session, - const TArray& LiveEventNames, - FOnGetLiveEvents Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriLiveEventList& LiveEvents) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(LiveEvents); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetLiveEvents(Session, LiveEventNames, successCallback, errorCallback); -} - -void USatoriClient::GetLiveEvents( - USatoriSession* Session, - const TArray& LiveEventNames, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/live-event"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - for (const FString& Name : LiveEventNames) - { - QueryParams.Add(TEXT("names"), Name); - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriLiveEventList LiveEvents = FSatoriLiveEventList(ResponseBody); - SuccessCallback(LiveEvents); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::GetMessages( - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor, - FOnGetMessages Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success](const FSatoriMessageList& Messages) - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(Messages); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - GetMessages(Session, Limit, Forward, Cursor, successCallback, errorCallback); -} - -void USatoriClient::GetMessages( - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/message"); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Setup the query parameters - TMultiMap QueryParams; - QueryParams.Add(TEXT("limit"), FString::FromInt(Limit)); - QueryParams.Add(TEXT("forward"), FSatoriUtils::BoolToString(Forward)); - if (!Cursor.IsEmpty()) { QueryParams.Add(TEXT("cursor"), FGenericPlatformHttp::UrlEncode(Cursor)); } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::GET, QueryParams, Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - const FSatoriMessageList Messages = FSatoriMessageList(ResponseBody); - SuccessCallback(Messages); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::UpdateMessage( - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime, - FOnUpdateMessageSent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - UpdateMessage(Session, MessageId, ReadTime, ConsumeTime, successCallback, errorCallback); -} - -void USatoriClient::UpdateMessage( - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/message/") + FGenericPlatformHttp::UrlEncode(MessageId); - - // Setup the request content - const TSharedPtr ContentJson = MakeShared(); - - ContentJson->SetNumberField(TEXT("read_time"), ReadTime.ToUnixTimestamp()); - ContentJson->SetNumberField(TEXT("consume_time"), ConsumeTime.ToUnixTimestamp()); - - // Serialize the request content - FString Content; - if (!FSatoriUtils::SerializeJsonObject(ContentJson, Content)) - { - // Handle JSON serialization failure - FSatoriUtils::HandleJsonSerializationFailure(ErrorCallback); - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, Content, ESatoriRequestMethod::PUT, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -void USatoriClient::DeleteMessage( - USatoriSession* Session, - const FString& MessageId, - FOnDeleteMessageSent Success, - FOnSatoriError Error) -{ - auto successCallback = [this, Success]() - { - if (!IsClientActive(this)) - { - return; - } - - Success.Broadcast(); - }; - - auto errorCallback = [this, Error](const FSatoriError& error) - { - if (!IsClientActive(this)) - { - return; - } - - Error.Broadcast(error); - }; - - DeleteMessage(Session, MessageId, successCallback, errorCallback); -} - -void USatoriClient::DeleteMessage( - USatoriSession* Session, - const FString& MessageId, - TFunction SuccessCallback, - TFunction ErrorCallback) -{ - // Setup the endpoint - const FString Endpoint = TEXT("/v1/message/") + FGenericPlatformHttp::UrlEncode(MessageId); - - // Verify the session - if (!FSatoriUtils::IsSessionValid(Session, ErrorCallback)) - { - return; - } - - // Make the request - const auto HttpRequest = MakeRequest(Endpoint, TEXT(""), ESatoriRequestMethod::DEL, TMultiMap(), Session->GetAuthToken()); - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - // Add the HttpRequest to ActiveRequests - ActiveRequests.Add(HttpRequest); - - // Bind the response callback and handle the response - HttpRequest->OnProcessRequestComplete().BindLambda([SuccessCallback, ErrorCallback, this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { - - if (!IsValidLowLevel()) - { - return; - } - - // Lock the ActiveRequests mutex to protect concurrent access - FScopeLock Lock(&ActiveRequestsMutex); - - if (ActiveRequests.Contains(Request)) - { - if (bSuccess && Response.IsValid()) - { - const FString ResponseBody = Response->GetContentAsString(); - - // Check if Request was successful - if (FSatoriUtils::IsResponseSuccessful(Response->GetResponseCode())) - { - // Check for Success Callback - if (SuccessCallback) - { - SuccessCallback(); - } - } - else - { - // Check for Error Callback - if (ErrorCallback) - { - const FSatoriError Error(ResponseBody); - ErrorCallback(Error); - } - } - } - else - { - // Handle Invalid Response - if (ErrorCallback) - { - const FSatoriError RequestError = FSatoriUtils::CreateRequestFailureError(); - ErrorCallback(RequestError); - } - } - - // Remove the HttpRequest from ActiveRequests - ActiveRequests.Remove(Request); - } - }); - - // Process the request - HttpRequest->ProcessRequest(); -} - -bool USatoriClient::IsClientActive(const USatoriClient* Client) -{ - return IsValid(Client) && Client->bIsActive == true; -} - -FString USatoriClient::ConstructURL(const FString& Endpoint) -{ - FString Protocol = bUseSSL ? TEXT("https") : TEXT("http"); - FString URL = FString::Printf(TEXT("%s://%s:%d%s"), *Protocol, *Hostname, Port, *Endpoint); - - return URL; -} - -TSharedRef USatoriClient::MakeRequest( - const FString& Endpoint, - const FString& Content, - ESatoriRequestMethod RequestMethod, - const TMultiMap& QueryParams, - const FString& SessionToken) -{ - // Append query parameters to the endpoint - FString ModifiedEndpoint = Endpoint; - if (QueryParams.Num() > 0) - { - FString QueryString = FSatoriUtils::BuildQueryString(QueryParams); - ModifiedEndpoint += "?" + QueryString; - } - - // Construct the URL - FString URL = ConstructURL(ModifiedEndpoint); - - return FSatoriUtils::MakeRequest(URL, Content, RequestMethod, SessionToken, Timeout); -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriError.cpp b/Satori/Source/SatoriUnreal/Private/SatoriError.cpp deleted file mode 100644 index b539462e5..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriError.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriError.h" -#include "SatoriUtils.h" - -ESatoriErrorCode FSatoriError::ConvertSatoriErrorCode(int32 CodeValue) -{ - switch (CodeValue) - { - case 0: - return ESatoriErrorCode::Ok; - case 1: - return ESatoriErrorCode::Cancelled; - case 2: - return ESatoriErrorCode::Unknown; - case 3: - return ESatoriErrorCode::InvalidArgument; - case 4: - return ESatoriErrorCode::DeadlineExceeded; - case 5: - return ESatoriErrorCode::NotFound; - case 6: - return ESatoriErrorCode::AlreadyExists; - case 7: - return ESatoriErrorCode::PermissionDenied; - case 8: - return ESatoriErrorCode::ResourceExhausted; - case 9: - return ESatoriErrorCode::FailedPrecondition; - case 10: - return ESatoriErrorCode::Aborted; - case 11: - return ESatoriErrorCode::OutOfRange; - case 12: - return ESatoriErrorCode::Unimplemented; - case 13: - return ESatoriErrorCode::Internal; - case 14: - return ESatoriErrorCode::Unavailable; - case 15: - return ESatoriErrorCode::DataLoss; - case 16: - return ESatoriErrorCode::Unauthenticated; - default: - return ESatoriErrorCode::Unknown; - } -} - -FSatoriError::FSatoriError(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - if (JsonObject->HasField(TEXT("message"))) - { - Message = JsonObject->GetStringField(TEXT("message")); - } - else - { - Message = TEXT("Invalid or missing 'message' field"); - } - - int32 CodeValue; - if (JsonObject->TryGetNumberField(TEXT("code"), CodeValue)) - { - //Code = static_cast(CodeValue); - Code = ConvertSatoriErrorCode(CodeValue); - } - else - { - Code = ESatoriErrorCode::Unknown; - } - } - else - { - Message = TEXT("Failed to parse JSON"); - Code = ESatoriErrorCode::Unknown; - } -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriEvent.cpp b/Satori/Source/SatoriUnreal/Private/SatoriEvent.cpp deleted file mode 100644 index 1836a5cb4..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriEvent.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriEvent.h" - -#include "SatoriUtils.h" - -FSatoriEvent::FSatoriEvent(const FString& JsonString) : FSatoriEvent(FSatoriUtils::DeserializeJsonObject(JsonString)) { -} - -FSatoriEvent::FSatoriEvent(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("id"), ID); - JsonObject->TryGetStringField(TEXT("value"), Value); - - FString TimestampString; - if (JsonObject->TryGetStringField(TEXT("timestamp"), TimestampString)) - { - FDateTime::ParseIso8601(*TimestampString, Timestamp); - } - - const TSharedPtr* MetadataObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("metadata"), MetadataObject)) { - for (const TPair>& Pair : (*MetadataObject)->Values) - { - Metadata.Add(*Pair.Key, Pair.Value->AsString()); - } - } - } -} - -FSatoriEvent::FSatoriEvent() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriExperiment.cpp b/Satori/Source/SatoriUnreal/Private/SatoriExperiment.cpp deleted file mode 100644 index 576627f4c..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriExperiment.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriExperiment.h" -#include "SatoriUtils.h" - -FSatoriExperiment::FSatoriExperiment(const FString& JsonString) : FSatoriExperiment(FSatoriUtils::DeserializeJsonObject(JsonString)) { -} - -FSatoriExperiment::FSatoriExperiment(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("value"), Value); - } -} - -FSatoriExperiment::FSatoriExperiment() -{ -} - -FSatoriExperimentList::FSatoriExperimentList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* ExperimentsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("experiments"), ExperimentsJsonArray)) - { - for (const TSharedPtr& ExperimentJsonValue : *ExperimentsJsonArray) - { - if (TSharedPtr ExperimentJsonObject = ExperimentJsonValue->AsObject()) - { - FSatoriExperiment Experiment(ExperimentJsonObject); - if (!Experiment.Name.IsEmpty()) - { - Experiments.Add(Experiment); - } - } - } - } - } -} - -FSatoriExperimentList::FSatoriExperimentList() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriFlag.cpp b/Satori/Source/SatoriUnreal/Private/SatoriFlag.cpp deleted file mode 100644 index 87a8a7155..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriFlag.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriFlag.h" -#include "SatoriUtils.h" - -FSatoriFlagValueChangeReason::FSatoriFlagValueChangeReason(const FString& JsonString) : FSatoriFlagValueChangeReason(FSatoriUtils::DeserializeJsonObject(JsonString)) -{ -} - -FSatoriFlagValueChangeReason::FSatoriFlagValueChangeReason(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("variant_name"), VariantName); - double typeNum; - if (JsonObject->TryGetNumberField(TEXT("type"), typeNum)) { - int typeInt = (int)typeNum; - if (typeInt >= static_cast(FSatoriFlagValueChangeReasonType::UNKNOWN) && typeInt <= static_cast(FSatoriFlagValueChangeReasonType::EXPERIMENT)) { - Type = static_cast(typeInt); - } - } - } -} - -FSatoriFlagValueChangeReason::FSatoriFlagValueChangeReason() -{ -} - -FSatoriFlag::FSatoriFlag(const FString& JsonString) : FSatoriFlag(FSatoriUtils::DeserializeJsonObject(JsonString)) -{ -} - -FSatoriFlag::FSatoriFlag(const TSharedPtr JsonObject) -{ - if(JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("value"), Value); - JsonObject->TryGetBoolField(TEXT("condition_changed"), bConditionChanged); - const TSharedPtr* ChangeReasonObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("change_reason"), ChangeReasonObject)) { - ChangeReason = FSatoriFlagValueChangeReason(*ChangeReasonObject); - } - } -} - -FSatoriFlag::FSatoriFlag() -{ -} - -FSatoriFlagList::FSatoriFlagList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* FlagsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("flags"), FlagsJsonArray)) - { - for (const TSharedPtr& FlagJsonValue : *FlagsJsonArray) - { - if (TSharedPtr FlagJsonObject = FlagJsonValue->AsObject()) - { - FSatoriFlag Flag(FlagJsonObject); - if (!Flag.Name.IsEmpty()) - { - Flags.Add(Flag); - } - } - } - } - } -} - -FSatoriFlagList::FSatoriFlagList() -{ -} - -FSatoriFlagOverrideValue::FSatoriFlagOverrideValue(const FString& JsonString) : FSatoriFlagOverrideValue(FSatoriUtils::DeserializeJsonObject(JsonString)) -{ -} - -FSatoriFlagOverrideValue::FSatoriFlagOverrideValue(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("variant_name"), VariantName); - JsonObject->TryGetStringField(TEXT("value"), Value); - double typeNum; - if (JsonObject->TryGetNumberField(TEXT("type"), typeNum)) { - int typeInt = (int)typeNum; - if (typeInt >= static_cast(FSatoriFlagOverrideType::FLAG) && typeInt <= static_cast(FSatoriFlagOverrideType::EXPERIMENT_PHASE_VARIANT_FLAG)) { - Type = static_cast(typeInt); - } - } - } -} - -FSatoriFlagOverrideValue::FSatoriFlagOverrideValue() -{ -} - -FSatoriFlagOverride::FSatoriFlagOverride(const FString& JsonString) : FSatoriFlagOverride(FSatoriUtils::DeserializeJsonObject(JsonString)) -{ -} - -FSatoriFlagOverride::FSatoriFlagOverride(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("flag_name"), FlagName); - const TArray>* OverridesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("overrides"), OverridesJsonArray)) - { - for (const TSharedPtr& OverrideJsonValue : *OverridesJsonArray) - { - if (TSharedPtr OverrideJsonObject = OverrideJsonValue->AsObject()) - { - FSatoriFlagOverrideValue Override(OverrideJsonObject); - Overrides.Add(Override); - } - } - } - } -} - -FSatoriFlagOverride::FSatoriFlagOverride() -{ -} - - -FSatoriFlagOverrideList::FSatoriFlagOverrideList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* FlagOverridesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("flags"), FlagOverridesJsonArray)) - { - for (const TSharedPtr& FlagOverrideJsonValue : *FlagOverridesJsonArray) - { - if (TSharedPtr FlagOverrideJsonObject = FlagOverrideJsonValue->AsObject()) - { - FSatoriFlagOverride FlagOverride(FlagOverrideJsonObject); - if (!FlagOverride.FlagName.IsEmpty()) - { - Flags.Add(FlagOverride); - } - } - } - } - } -} - -FSatoriFlagOverrideList::FSatoriFlagOverrideList() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriLiveEvent.cpp b/Satori/Source/SatoriUnreal/Private/SatoriLiveEvent.cpp deleted file mode 100644 index fd09bfddc..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriLiveEvent.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriLiveEvent.h" -#include "SatoriUtils.h" - -FSatoriLiveEvent::FSatoriLiveEvent(const FString& JsonString) : FSatoriLiveEvent(FSatoriUtils::DeserializeJsonObject(JsonString)) { -} - -FSatoriLiveEvent::FSatoriLiveEvent(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("name"), Name); - JsonObject->TryGetStringField(TEXT("description"), Description); - JsonObject->TryGetStringField(TEXT("value"), Value); - JsonObject->TryGetNumberField(TEXT("active_start_time_sec"), ActiveStartTimeSec); - JsonObject->TryGetNumberField(TEXT("active_end_time_sec"), ActiveEndTimeSec); - JsonObject->TryGetStringField(TEXT("id"), ID); - JsonObject->TryGetNumberField(TEXT("start_time_sec"), StartTimeSec); - JsonObject->TryGetNumberField(TEXT("end_time_sec"), EndTimeSec); - JsonObject->TryGetNumberField(TEXT("duration_sec"), DurationSec); - JsonObject->TryGetStringField(TEXT("reset_cron"), ResetCron); - } -} - -FSatoriLiveEvent::FSatoriLiveEvent() -{ -} - -FSatoriLiveEventList::FSatoriLiveEventList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* LiveEventsJsonArray; - if (JsonObject->TryGetArrayField(TEXT("live_events"), LiveEventsJsonArray)) - { - for (const TSharedPtr& LiveEventJsonValue : *LiveEventsJsonArray) - { - if(TSharedPtr LiveEventJsonObject = LiveEventJsonValue->AsObject()) - { - FSatoriLiveEvent LiveEvent(LiveEventJsonObject); - if (!LiveEvent.Name.IsEmpty()) - { - LiveEvents.Add(LiveEvent); - } - } - } - } - } -} - -FSatoriLiveEventList::FSatoriLiveEventList() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriLogger.cpp b/Satori/Source/SatoriUnreal/Private/SatoriLogger.cpp deleted file mode 100644 index 71825fddc..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriLogger.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriLogger.h" - -DEFINE_LOG_CATEGORY(LogSatoriUnreal); - -ESatoriLogLevel USatoriLogger::CurrentLogLevel = ESatoriLogLevel::Info; -bool USatoriLogger::bLoggingEnabled = false; - -USatoriLogger::USatoriLogger() -{ - -} - -void USatoriLogger::SetLogLevel(ESatoriLogLevel InLogLevel) -{ - CurrentLogLevel = InLogLevel; -} - -bool USatoriLogger::IsLoggable(ESatoriLogLevel InLogLevel) -{ - // Logging Disabled check - if (!bLoggingEnabled) - return false; - - // Debug - if(CurrentLogLevel == ESatoriLogLevel::Debug) - { - if(InLogLevel == ESatoriLogLevel::Debug) - return true; - - if(InLogLevel == ESatoriLogLevel::Info) - return true; - - if(InLogLevel == ESatoriLogLevel::Warn) - return true; - - if(InLogLevel == ESatoriLogLevel::Error) - return true; - - if(InLogLevel == ESatoriLogLevel::Fatal) - return true; - } - - // Info - if(CurrentLogLevel == ESatoriLogLevel::Info) - { - if(InLogLevel == ESatoriLogLevel::Info) - return true; - - if(InLogLevel == ESatoriLogLevel::Warn) - return true; - - if(InLogLevel == ESatoriLogLevel::Error) - return true; - - if(InLogLevel == ESatoriLogLevel::Fatal) - return true; - } - - // Warn - if(CurrentLogLevel == ESatoriLogLevel::Warn) - { - if(InLogLevel == ESatoriLogLevel::Warn) - return true; - - if(InLogLevel == ESatoriLogLevel::Error) - return true; - - if(InLogLevel == ESatoriLogLevel::Fatal) - return true; - } - - // Error - if(CurrentLogLevel == ESatoriLogLevel::Error) - { - if(InLogLevel == ESatoriLogLevel::Error) - return true; - - if(InLogLevel == ESatoriLogLevel::Fatal) - return true; - } - - // Fatal - if(CurrentLogLevel == ESatoriLogLevel::Fatal) - { - if(InLogLevel == ESatoriLogLevel::Fatal) - return true; - } - - return false; -} - -void USatoriLogger::Log(ESatoriLogLevel InLogLevel, const FString& Message) -{ - if (IsLoggable(InLogLevel)) - { - switch (InLogLevel) - { - case ESatoriLogLevel::Debug: - UE_LOG(LogSatoriUnreal, Display, TEXT("%s"), *Message); - break; - case ESatoriLogLevel::Info: - UE_LOG(LogSatoriUnreal, Display, TEXT("%s"), *Message); - break; - case ESatoriLogLevel::Warn: - UE_LOG(LogSatoriUnreal, Warning, TEXT("%s"), *Message); - break; - case ESatoriLogLevel::Error: - UE_LOG(LogSatoriUnreal, Error, TEXT("%s"), *Message); - break; - case ESatoriLogLevel::Fatal: - UE_LOG(LogSatoriUnreal, Fatal, TEXT("%s"), *Message); - break; - default: - break; - } - } -} - -void USatoriLogger::EnableLogging(bool bEnable) -{ - bLoggingEnabled = bEnable; -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriMessage.cpp b/Satori/Source/SatoriUnreal/Private/SatoriMessage.cpp deleted file mode 100644 index abfae2f77..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriMessage.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriMessage.h" -#include "SatoriUtils.h" - -FSatoriMessage::FSatoriMessage(const FString& JsonString) : FSatoriMessage(FSatoriUtils::DeserializeJsonObject(JsonString)) { -} - -FSatoriMessage::FSatoriMessage(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - JsonObject->TryGetStringField(TEXT("schedule_id"), ScheduleID); - JsonObject->TryGetNumberField(TEXT("send_time"), SendTime); - JsonObject->TryGetNumberField(TEXT("create_time"), CreateTime); - JsonObject->TryGetNumberField(TEXT("update_time"), UpdateTime); - JsonObject->TryGetNumberField(TEXT("read_time"), ReadTime); - JsonObject->TryGetNumberField(TEXT("consume_time"), ConsumeTime); - JsonObject->TryGetStringField(TEXT("text"), Text); - JsonObject->TryGetStringField(TEXT("id"), ID); - JsonObject->TryGetStringField(TEXT("title"), Title); - JsonObject->TryGetStringField(TEXT("image_url"), ImageURL); - const TSharedPtr* MetadataObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("metadata"), MetadataObject)) { - for (const TPair>& Pair : (*MetadataObject)->Values) - { - Metadata.Add(*Pair.Key, Pair.Value->AsString()); - } - } - } -} - -FSatoriMessage::FSatoriMessage() -{ -} - -FSatoriMessageList::FSatoriMessageList(const FString& JsonString) -{ - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) - { - const TArray>* MessagesJsonArray; - if (JsonObject->TryGetArrayField(TEXT("messages"), MessagesJsonArray)) - { - for (const TSharedPtr& MessageJsonValue : *MessagesJsonArray) - { - if(TSharedPtr MessageJsonObject = MessageJsonValue->AsObject()) - { - FSatoriMessage Message(MessageJsonObject); - if (!Message.ID.IsEmpty()) - { - Messages.Add(Message); - } - } - } - } - } -} - -FSatoriMessageList::FSatoriMessageList() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriProperties.cpp b/Satori/Source/SatoriUnreal/Private/SatoriProperties.cpp deleted file mode 100644 index e743d98ed..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriProperties.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriProperties.h" -#include "SatoriUtils.h" - -FSatoriProperties::FSatoriProperties(const FString& JsonString) : FSatoriProperties(FSatoriUtils::DeserializeJsonObject(JsonString)) { -} - -FSatoriProperties::FSatoriProperties(const TSharedPtr JsonObject) -{ - if (JsonObject.IsValid()) - { - const TSharedPtr* MetadataObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("default"), MetadataObject)) { - for (const TPair>& Pair : (*MetadataObject)->Values) - { - DefaultProperties.Add(*Pair.Key, Pair.Value->AsString()); - } - } - - MetadataObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("computed"), MetadataObject)) { - for (const TPair>& Pair : (*MetadataObject)->Values) - { - ComputedProperties.Add(*Pair.Key, Pair.Value->AsString()); - } - } - - MetadataObject = nullptr; - if (JsonObject->TryGetObjectField(TEXT("custom"), MetadataObject)) { - for (const TPair>& Pair : (*MetadataObject)->Values) - { - CustomProperties.Add(*Pair.Key, Pair.Value->AsString()); - } - } - } -} - -FSatoriProperties::FSatoriProperties() -{ -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriSession.cpp b/Satori/Source/SatoriUnreal/Private/SatoriSession.cpp deleted file mode 100644 index 8d46987a8..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriSession.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriSession.h" - -#include "SatoriUtils.h" - - -USatoriSession* USatoriSession::SetupSession(const FString& AuthResponse) -{ - USatoriSession* ResultSession = NewObject(); - TSharedPtr JsonObject = MakeShareable(new FJsonObject()); - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(AuthResponse); - - if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - FString Token = JsonObject->GetStringField(TEXT("token")); - FString RefreshToken = JsonObject->GetStringField(TEXT("refresh_token")); - - ResultSession->SessionData.AuthToken = Token; - ResultSession->_AuthToken = Token; - ResultSession->SessionData.RefreshToken = RefreshToken; - ResultSession->_RefreshToken = RefreshToken; - - const TSharedPtr* PropertiesJson = nullptr; - if (JsonObject->TryGetObjectField(TEXT("properties"), PropertiesJson)) - { - FSatoriProperties Properties = FSatoriProperties(*PropertiesJson); - ResultSession->SessionData.Properties = Properties; - ResultSession->_Properties = Properties; - } - return ResultSession; - } - else - { - SATORI_LOG_ERROR(TEXT("Failed to deserialize Satori Session JSON object")); - } - return nullptr; -} - -const FString USatoriSession::GetAuthToken() const -{ - return _AuthToken; -} - -const FString USatoriSession::GetRefreshToken() const -{ - return _RefreshToken; -} - -const FSatoriProperties USatoriSession::GetProperties() const -{ - return _Properties; -} - -USatoriSession* USatoriSession::RestoreSession(FString Token, FString RefreshToken) -{ - USatoriSession* ResultSession = NewObject(); - ResultSession->SessionData.AuthToken = Token; - ResultSession->_AuthToken = Token; - ResultSession->SessionData.RefreshToken = RefreshToken; - ResultSession->_RefreshToken = RefreshToken; - return ResultSession; -} diff --git a/Satori/Source/SatoriUnreal/Private/SatoriUnreal.cpp b/Satori/Source/SatoriUnreal/Private/SatoriUnreal.cpp deleted file mode 100644 index 59abb1ddd..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriUnreal.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriUnreal.h" -#include "Modules/ModuleManager.h" - -void FSatoriUnrealModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - - //NLogger::init(std::make_shared(), NLogLevel::Debug); - -} - -void FSatoriUnrealModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FSatoriUnrealModule, SatoriUnreal) diff --git a/Satori/Source/SatoriUnreal/Private/SatoriUserSession.cpp b/Satori/Source/SatoriUnreal/Private/SatoriUserSession.cpp deleted file mode 100644 index 20336ce79..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriUserSession.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriUserSession.h" - -FSatoriUserSession::FSatoriUserSession() -{ - -} \ No newline at end of file diff --git a/Satori/Source/SatoriUnreal/Private/SatoriUtils.cpp b/Satori/Source/SatoriUnreal/Private/SatoriUtils.cpp deleted file mode 100644 index 69ae392ab..000000000 --- a/Satori/Source/SatoriUnreal/Private/SatoriUtils.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SatoriUtils.h" -#include "SatoriLogger.h" -#include "Dom/JsonObject.h" -#include "Misc/EngineVersionComparison.h" -#include "Interfaces/IHttpResponse.h" - -DEFINE_LOG_CATEGORY_STATIC(LogSatoriUtils, Log, Log); - - void FSatoriUtils::ProcessRequestComplete(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, const TFunction& SuccessCallback, const TFunction& ErrorCallback) - { - if (bSuccess && Response.IsValid()) - { - const int32 ResponseCode = Response->GetResponseCode(); - const FString ResponseBody = Response->GetContentAsString(); - - if (ResponseCode == 200) - { - SATORI_LOG_DEBUG(FString::Printf(TEXT("Request Successful: %s"), *ResponseBody)); - if (SuccessCallback) - { - SuccessCallback(ResponseBody); - } - } - else - { - SATORI_LOG_WARN(FString::Printf(TEXT("Response (Code: %d) - Contents: %s"), ResponseCode, *ResponseBody)); - const FSatoriError Error(ResponseBody); - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - } - else - { - // Handle request failure - SATORI_LOG_ERROR(TEXT("Failed to process request.")); - - if (Request.IsValid()) - { - SATORI_LOG_DEBUG(FString::Printf(TEXT("Request URL: %s"), *(Request->GetURL()))); - } - - FSatoriError Error; - Error.Code = ESatoriErrorCode::Unknown; - Error.Message = TEXT("Failed to proccess request. Request failed."); - - if (ErrorCallback) - { - ErrorCallback(Error); - } - } - } - - void FSatoriUtils::HandleJsonSerializationFailure(TFunction ErrorCallback) - { - SATORI_LOG_ERROR(TEXT("Failed to generate request content.")); - FSatoriError Error; - Error.Code = ESatoriErrorCode::Unknown; - Error.Message = TEXT("Failed to generate request content."); - ErrorCallback(Error); - } - - bool FSatoriUtils::IsSessionValid(const USatoriSession* Session, TFunction ErrorCallback) - { - if (!Session || Session->SessionData.AuthToken.IsEmpty()) - { - SATORI_LOG_ERROR("Invalid session or session data."); - - FSatoriError Error; - Error.Message = "Invalid session or session data."; - ErrorCallback(Error); - return false; - } - - return true; - } - - bool FSatoriUtils::IsResponseSuccessful(int32 ResponseCode) - { - return ResponseCode == 200; - } - - FSatoriError FSatoriUtils::CreateRequestFailureError() - { - SATORI_LOG_ERROR(TEXT("Failed to proccess request. Request failed.")); - FSatoriError Error; - Error.Code = ESatoriErrorCode::Unknown; - Error.Message = TEXT("Failed to proccess request. Request failed."); - return Error; - } - - TSharedRef FSatoriUtils::MakeRequest(const FString& URL, const FString& Content, ESatoriRequestMethod RequestMethod, const FString& SessionToken, float Timeout) - { - FHttpModule* HttpModule = &FHttpModule::Get(); - - // Create the HttpRequest -#if ENGINE_MAJOR_VERSION <= 4 && ENGINE_MINOR_VERSION <= 25 - TSharedRef Request = HttpModule->CreateRequest(); -#else - TSharedRef HttpRequest = HttpModule->CreateRequest(); -#endif - - HttpRequest->SetURL(URL); - HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - HttpRequest->SetTimeout(Timeout); // Exposed to end user - - FString VerbString = ESatoriRequesMethodToFString(RequestMethod); - if (!VerbString.IsEmpty()) - { - HttpRequest->SetVerb(VerbString); - } - - // Set the content if it is not empty - if (!Content.IsEmpty()) - { - HttpRequest->SetContentAsString(Content); - } - - // Add authorization header if session token is provided - if (!SessionToken.IsEmpty()) - { - FString AuthorizationHeader = FString::Printf(TEXT("Bearer %s"), *SessionToken); - HttpRequest->SetHeader(TEXT("Authorization"), AuthorizationHeader); - } - - //SATORI_LOG_INFO(TEXT("...")); - //SATORI_LOG_INFO(FString::Printf(TEXT("Making Request to %s"), *Endpoint)); - SATORI_LOG_INFO(FString::Printf(TEXT("Making %s request to %s with content: %s"), *VerbString, *URL, *Content)); - return HttpRequest; - } diff --git a/Satori/Source/SatoriUnreal/Public/SatoriClient.h b/Satori/Source/SatoriUnreal/Public/SatoriClient.h deleted file mode 100644 index c16e8527b..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriClient.h +++ /dev/null @@ -1,416 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "Interfaces/IHttpRequest.h" -#include "HttpModule.h" -#include "SatoriError.h" -#include "SatoriSession.h" -#include "SatoriEvent.h" -#include "SatoriExperiment.h" -#include "SatoriLiveEvent.h" -#include "SatoriMessage.h" -#include "SatoriFlag.h" -#include "SatoriClient.generated.h" - -namespace Satori {} -using namespace Satori; -enum class ESatoriRequestMethod:uint8; - -// Delegates -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriAuthUpdate, USatoriSession*, LoginData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAuthLogoutSent); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeleteIdentitySent); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriError, const FSatoriError&, ErrorData); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPostEventSent); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetExperiments, const FSatoriExperimentList&, Experiments); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetFlags, const FSatoriFlagList&, Flags); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetFlagOverrides, const FSatoriFlagOverrideList&, FlagOverrides); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetLiveEvents, const FSatoriLiveEventList&, LiveEvents); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetProperties, const FSatoriProperties&, Properties); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUpdatePropertiesSent); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGetMessages, const FSatoriMessageList&, Messages); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUpdateMessageSent); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeleteMessageSent); - -UENUM(BlueprintType) -enum class ESatoriRequestMethod : uint8 -{ - GET, - POST, - DEL, - PUT, -}; - -/** - * - */ -UCLASS(Blueprintable, BlueprintType, meta=(BlueprintSpawnableComponent)) -class SATORIUNREAL_API USatoriClient : public UObject -{ - GENERATED_BODY() - -private: - - // End user can set this - float Timeout = 10.0f; - - bool bEnableDebug; -protected: - - // Variables - FString Hostname; - int32 Port; - FString ServerKey; - bool bUseSSL; - FHttpModule* HttpModule; - -public: - void InitializeClient( - const FString& InHostname, - int32 InPort, - const FString& InServerKey, - bool bInUseSSL, - bool EnableDebug - ); - - UPROPERTY() - bool bIsActive; - - // Initialize System, this has to be called first, done via the Library Action instead (removed BlueprintCallable) - UFUNCTION(Category = "Satori|Initialize") - void InitializeSystem(const FString& InServerKey, const FString& Host, int32 InPort, bool UseSSL, bool EnableDebug); - - /** - * Disconnects the client. This function kills all outgoing exchanges immediately without waiting. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Client", meta = (DeprecatedFunction, DeprecationMessage = "Use CancelAllRequests instead")) - void Disconnect(); - - /** - * Cancels all Requests. This function kills all outgoing exchanges immediately without waiting. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Client") - void CancelAllRequests(); - - /** - * Destroys the Client. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Client") - void Destroy(); - - // Manage Timeout - UFUNCTION(BlueprintCallable, Category = "Satori|Client") - void SetTimeout(float InTimeout); - - UFUNCTION(BlueprintCallable, Category = "Satori|Client") - float GetTimeout(); - - // Event that is called on cleanup - virtual void BeginDestroy() override; - - /** - * Creates a default client to interact with Satori server. - * - * @param ServerKey Server key should match the one on the Satori Server. (Selfhosted default for dev satori in docker, this will be found here http://localhost:7451/settings/server-keys) - * @param Host The endpoint host name. - * @param Port The port to use, default is 7450. - * @param UseSSL Use "https" scheme if you've setup SSL. - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Client") - static USatoriClient* CreateDefaultClient( - const FString& ServerKey = "d93def3b-fcd2-424e-b719-cd93b852fc3c", - const FString& Host = "localhost", - int32 Port = 7450, - bool UseSSL = false, - bool EnableDebug = true - ); - - // --- Authentication --- // - - /** - * Authenticate to get a satori session. - * - * @param ID Must be between eight and 128 characters (inclusive). Must be an alphanumeric string with only underscores and hyphens allowed. - * @param DefaultProperties Optional default properties to update with this call. If not set, properties are left as they are on the server. - * @param CustomProperties Optional custom properties to update with this call. If not set, properties are left as they are on the server. - * @param bNoSession Modifies the request to only create/update an identity without creating a new session. If set to 'true' the response won't include a token and a refresh token. - * @param Success Delegate called upon successful authentication, providing the user session. - * @param Error Delegate called on authentication failure with error details. - */ - - UFUNCTION(Category = "Satori|Authentication") - void Authenticate( - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error - ); - - void Authenticate( - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bNoSession, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void AuthenticateRefresh( - USatoriSession* Session, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error - ); - - void AuthenticateRefresh( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void AuthenticateLogout( - USatoriSession* Session, - FOnAuthLogoutSent Success, - FOnSatoriError Error - ); - - void AuthenticateLogout( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void Identify( - USatoriSession* Session, - const FString& ID, - const TMap& defaultProperties, - const TMap& customProperties, - FOnSatoriAuthUpdate Success, - FOnSatoriError Error - ); - - void Identify( - USatoriSession* Session, - const FString& ID, - const TMap& DefaultProperties, - const TMap& CustomProperties, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void ListIdentityProperties( - USatoriSession* Session, - FOnGetProperties Success, - FOnSatoriError Error - ); - - void ListIdentityProperties( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void UpdateProperties( - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute, - FOnUpdatePropertiesSent Success, - FOnSatoriError Error - ); - - void UpdateProperties( - USatoriSession* Session, - const TMap& DefaultProperties, - const TMap& CustomProperties, - const bool bRecompute, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Authentication") - void DeleteIdentity( - USatoriSession* Session, - FOnDeleteIdentitySent Success, - FOnSatoriError Error - ); - - void DeleteIdentity( - USatoriSession* Session, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - - // --- Interface --- // - - UFUNCTION(Category = "Satori|Events") - void PostEvent( - USatoriSession* Session, - const TArray& Events, - FOnPostEventSent Success, - FOnSatoriError Error - ); - - void PostEvent( - USatoriSession* Session, - const TArray& Events, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Experiments") - void GetExperiments( - USatoriSession* Session, - const TArray& Names, - FOnGetExperiments Success, - FOnSatoriError Error - ); - - void GetExperiments( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Flags") - void GetFlags( - USatoriSession* Session, - const TArray& Names, - FOnGetFlags Success, - FOnSatoriError Error - ); - - void GetFlags( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Flags") - void GetFlagOverrides( - USatoriSession* Session, - const TArray& Names, - FOnGetFlagOverrides Success, - FOnSatoriError Error - ); - - void GetFlagOverrides( - USatoriSession* Session, - const TArray& Names, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|LiveEvents") - void GetLiveEvents( - USatoriSession* Session, - const TArray& LiveEventNames, - FOnGetLiveEvents Success, - FOnSatoriError Error - ); - - void GetLiveEvents( - USatoriSession* Session, - const TArray& LiveEventNames, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Messages") - void GetMessages( - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor, - FOnGetMessages Success, - FOnSatoriError Error - ); - - void GetMessages( - USatoriSession* Session, - int32 Limit, - bool Forward, - const FString& Cursor, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Messages") - void UpdateMessage( - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime, - FOnUpdateMessageSent Success, - FOnSatoriError Error - ); - - void UpdateMessage( - USatoriSession* Session, - const FString& MessageId, - const FDateTime ReadTime, - const FDateTime ConsumeTime, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - UFUNCTION(Category = "Satori|Messages") - void DeleteMessage( - USatoriSession* Session, - const FString& MessageId, - FOnDeleteMessageSent Success, - FOnSatoriError Error - ); - - void DeleteMessage( - USatoriSession* Session, - const FString& MessageId, - TFunction SuccessCallback, - TFunction ErrorCallback - ); - - // --- Utilities --- // - - static bool IsClientActive(const USatoriClient* Client); - -private: - FString ConstructURL(const FString& Endpoint); - // Make HTTP request - TSharedRef MakeRequest( - const FString& Endpoint, - const FString& Content, - ESatoriRequestMethod RequestMethod, - const TMultiMap& QueryParams, - const FString& SessionToken - ); - -private: - // Requests - TArray ActiveRequests; - FCriticalSection ActiveRequestsMutex; -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriError.h b/Satori/Source/SatoriUnreal/Public/SatoriError.h deleted file mode 100644 index dd9d9059d..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriError.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriError.generated.h" - -// Error Data -UENUM(BlueprintType) -enum class ESatoriErrorCode : uint8 -{ - Ok = 0 UMETA(DisplayName = "Ok"), - Cancelled = 1 UMETA(DisplayName = "Cancelled"), - Unknown = 2 UMETA(DisplayName = "Unknown"), - InvalidArgument = 3 UMETA(DisplayName = "Invalid Argument"), - DeadlineExceeded = 4 UMETA(DisplayName = "Deadline Exceeded"), - NotFound = 5 UMETA(DisplayName = "Not Found"), - AlreadyExists = 6 UMETA(DisplayName = "Already Exists"), - PermissionDenied = 7 UMETA(DisplayName = "Permission Denied"), - ResourceExhausted = 8 UMETA(DisplayName = "Resource Exhausted"), - FailedPrecondition = 9 UMETA(DisplayName = "Failed Precondition"), - Aborted = 10 UMETA(DisplayName = "Aborted"), - OutOfRange = 11 UMETA(DisplayName = "Out Of Range"), - Unimplemented = 12 UMETA(DisplayName = "Unimplemented"), - Internal = 13 UMETA(DisplayName = "Internal"), - Unavailable = 14 UMETA(DisplayName = "Unavailable"), - DataLoss = 15 UMETA(DisplayName = "Data Loss"), - Unauthenticated = 16 UMETA(DisplayName = "Unauthenticated") -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriError -{ - GENERATED_BODY() - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori Error") - FString Message; - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori Error") - ESatoriErrorCode Code; - - FSatoriError(const FString& JsonString); - FSatoriError(): Code(ESatoriErrorCode::Unknown) { } - - ESatoriErrorCode ConvertSatoriErrorCode(int32 CodeValue); - - -}; \ No newline at end of file diff --git a/Satori/Source/SatoriUnreal/Public/SatoriEvent.h b/Satori/Source/SatoriUnreal/Public/SatoriEvent.h deleted file mode 100644 index ff1b58c66..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriEvent.h +++ /dev/null @@ -1,54 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriProperties.h" -#include "SatoriEvent.generated.h" - - -// Events -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriEvent -{ - GENERATED_BODY() - - // Event name. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Events") - FString Name; - - // Optional event ID assigned by the client, used to de-duplicate in retransmission scenarios. - // If not supplied the server will assign a randomly generated unique event identifier. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Events") - FString ID; - - // Optional event metadata - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Events") - TMap Metadata; - - // Optional value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Events") - FString Value; - - // The time when the event was triggered on the producer side. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Events") - FDateTime Timestamp; - - FSatoriEvent(const FString& JsonString); - FSatoriEvent(const TSharedPtr JsonObject); - FSatoriEvent(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriExperiment.h b/Satori/Source/SatoriUnreal/Public/SatoriExperiment.h deleted file mode 100644 index da93178eb..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriExperiment.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriExperiment.generated.h" - - -// Experiments -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriExperiment -{ - GENERATED_BODY() - - // Experiment name. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Experiments") - FString Name; - - // Value associated with this Experiment. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Experiments") - FString Value; - - FSatoriExperiment(const FString& JsonString); - FSatoriExperiment(const TSharedPtr JsonObject); - FSatoriExperiment(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriExperimentList -{ - GENERATED_BODY() - - // Flags. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Experiments") - TArray Experiments; - - FSatoriExperimentList(const FString& JsonString); - FSatoriExperimentList(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriFlag.h b/Satori/Source/SatoriUnreal/Public/SatoriFlag.h deleted file mode 100644 index d120f0077..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriFlag.h +++ /dev/null @@ -1,166 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriFlag.generated.h" - - -// Flags -UENUM(BlueprintType) -enum class FSatoriFlagValueChangeReasonType : uint8 -{ - UNKNOWN = 0, - FLAG_VARIANT = 1, - LIVE_EVENT = 2, - EXPERIMENT = 3 -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlagValueChangeReason -{ - GENERATED_BODY() - - // The type of the configuration that declared the override. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FSatoriFlagValueChangeReasonType Type = FSatoriFlagValueChangeReasonType::UNKNOWN; - - // The name of the configuration that overrides the flag value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString Name; - - // The variant name of the configuration that overrides the flag value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString VariantName; - - FSatoriFlagValueChangeReason(const FString& JsonString); - FSatoriFlagValueChangeReason(const TSharedPtr JsonObject); - FSatoriFlagValueChangeReason(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlag -{ - GENERATED_BODY() - - // Flag name. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString Name; - - // Value associated with this flag. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString Value; - - // Whether the value for this flag has conditionally changed from the default state. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - bool bConditionChanged = false; - - // The origin of change on the flag value returned. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FSatoriFlagValueChangeReason ChangeReason; - - FSatoriFlag(const FString& JsonString); - FSatoriFlag(const TSharedPtr JsonObject); - FSatoriFlag(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlagList -{ - GENERATED_BODY() - - // Flags. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - TArray Flags; - - FSatoriFlagList(const FString& JsonString); - FSatoriFlagList(); // Default Constructor -}; - - -// Flag Overrides - -UENUM(BlueprintType) -enum class FSatoriFlagOverrideType : uint8 -{ - FLAG = 0, - FLAG_VARIANT = 1, - LIVE_EVENT_FLAG = 2, - LIVE_EVENT_FLAG_VARIANT = 3, - EXPERIMENT_PHASE_VARIANT_FLAG = 4 -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlagOverrideValue -{ - GENERATED_BODY() - - // The type of the configuration that declared the override. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FSatoriFlagOverrideType Type = FSatoriFlagOverrideType::FLAG; - - // The name of the configuration that overrides the flag value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString Name; - - // The variant name of the configuration that overrides the flag value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString VariantName; - - // The value of the configuration that overrides the flag. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString Value; - - // The create time of the configuration that overrides the flag. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - int64 CreateTimeSec; - - FSatoriFlagOverrideValue(const FString& JsonString); - FSatoriFlagOverrideValue(const TSharedPtr JsonObject); - FSatoriFlagOverrideValue(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlagOverride -{ - GENERATED_BODY() - - // Flag name - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - FString FlagName; - - // The list of configuration that affect the value of the flag. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - TArray Overrides; - - FSatoriFlagOverride(const FString& JsonString); - FSatoriFlagOverride(const TSharedPtr JsonObject); - FSatoriFlagOverride(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriFlagOverrideList -{ - GENERATED_BODY() - - // Flags. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Flags") - TArray Flags; - - FSatoriFlagOverrideList(const FString& JsonString); - FSatoriFlagOverrideList(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriLiveEvent.h b/Satori/Source/SatoriUnreal/Public/SatoriLiveEvent.h deleted file mode 100644 index 263c96f96..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriLiveEvent.h +++ /dev/null @@ -1,85 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriLiveEvent.generated.h" - - -// LiveEvents -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriLiveEvent -{ - GENERATED_BODY() - - // Name. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - FString Name; - - // Description. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - FString Description; - - // Event value. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - FString Value; - - // Start time of current event run. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - int64 ActiveStartTimeSec = -1; - - // End time of current event run. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - int64 ActiveEndTimeSec = -1; - - // The live event identifier. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - FString ID; - - // Start time. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - int64 StartTimeSec = -1; - - // End time, 0 if it repeats forever. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - int64 EndTimeSec = -1; - - // Duration in seconds. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - int64 DurationSec = -1; - - // Reset CRON schedule, if configured. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - FString ResetCron; - - FSatoriLiveEvent(const FString& JsonString); - FSatoriLiveEvent(const TSharedPtr JsonObject); - FSatoriLiveEvent(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriLiveEventList -{ - GENERATED_BODY() - - // Flags. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|LiveEvents") - TArray LiveEvents; - - FSatoriLiveEventList(const FString& JsonString); - FSatoriLiveEventList(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriLogger.h b/Satori/Source/SatoriUnreal/Public/SatoriLogger.h deleted file mode 100644 index 53c5f3c49..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriLogger.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "SatoriLogger.generated.h" - -//DECLARE_LOG_CATEGORY_EXTERN(LogSatori, Log, All); -DECLARE_LOG_CATEGORY_EXTERN(LogSatoriUnreal, Log, All); - -UENUM(BlueprintType, Category = "Satori") -enum class ESatoriLogLevel : uint8 -{ - Debug, - Info, - Warn, - Error, - Fatal -}; - -/** - * - */ -UCLASS() -class SATORIUNREAL_API USatoriLogger : public UObject -{ - GENERATED_BODY() - -public: - USatoriLogger(); - - UFUNCTION(BlueprintCallable, Category = "Satori") - static void SetLogLevel(ESatoriLogLevel InLogLevel); - - UFUNCTION(BlueprintCallable, Category = "Satori") - static void Log(ESatoriLogLevel InLogLevel, const FString& Message); - - UFUNCTION(BlueprintCallable, Category = "Satori") - static void EnableLogging(bool bEnable); - -private: - static ESatoriLogLevel CurrentLogLevel; - static bool bLoggingEnabled; - static bool IsLoggable(ESatoriLogLevel InLogLevel); -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriLoggingMacros.h b/Satori/Source/SatoriUnreal/Public/SatoriLoggingMacros.h deleted file mode 100644 index 57df9f783..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriLoggingMacros.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -// Toggle logging on/off by defining SATORI_LOGS_ENABLED -#define SATORI_LOGS_ENABLED - -#ifdef SATORI_LOGS_ENABLED - - #define SATORI_LOG_DEBUG(Message) \ - USatoriLogger::Log(ESatoriLogLevel::Debug, Message) - - #define SATORI_LOG_INFO(Message) \ - USatoriLogger::Log(ESatoriLogLevel::Info, Message) - - #define SATORI_LOG_WARN(Message) \ - USatoriLogger::Log(ESatoriLogLevel::Warn, Message) - - #define SATORI_LOG_ERROR(Message) \ - USatoriLogger::Log(ESatoriLogLevel::Error, Message) - - #define SATORI_LOG_FATAL(Message) \ - USatoriLogger::Log(ESatoriLogLevel::Fatal, Message) - -#else - - // Define empty macros if logging is disabled - #define SATORI_LOG_DEBUG(Message) - #define SATORI_LOG_INFO(Message) - #define SATORI_LOG_WARN(Message) - #define SATORI_LOG_ERROR(Message) - #define SATORI_LOG_FATAL(Message) - -#endif // SATORI_LOGS_ENABLED \ No newline at end of file diff --git a/Satori/Source/SatoriUnreal/Public/SatoriMessage.h b/Satori/Source/SatoriUnreal/Public/SatoriMessage.h deleted file mode 100644 index 013a882a3..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriMessage.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriMessage.generated.h" - - -// Messages -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriMessage -{ - GENERATED_BODY() - - // The identifier of the schedule. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - FString ScheduleID; - - // The send time for the message. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - int64 SendTime; - - // A key-value pairs of metadata. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - TMap Metadata; - - // The time the message was created. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - int64 CreateTime; - - // The time the message was updated. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - int64 UpdateTime; - - // The time the message was read by the client. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - int64 ReadTime; - - // The time the message was consumed by the identity. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - int64 ConsumeTime; - - // The message's text. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - FString Text; - - // The message's unique identifier. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - FString ID; - - // The message's title. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - FString Title; - - // The message's image url. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - FString ImageURL; - - FSatoriMessage(const FString& JsonString); - FSatoriMessage(const TSharedPtr JsonObject); - FSatoriMessage(); // Default Constructor -}; - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriMessageList -{ - GENERATED_BODY() - - // Flags. - UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Satori|Messages") - TArray Messages; - - FSatoriMessageList(const FString& JsonString); - FSatoriMessageList(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriProperties.h b/Satori/Source/SatoriUnreal/Public/SatoriProperties.h deleted file mode 100644 index 913d94abd..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriProperties.h +++ /dev/null @@ -1,41 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriProperties.generated.h" - - -// Properties -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriProperties -{ - GENERATED_BODY() - // Event default properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - TMap DefaultProperties; - // Event computed properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - TMap ComputedProperties; - // Event custom properties. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - TMap CustomProperties; - - FSatoriProperties(const FString& JsonString); - FSatoriProperties(const TSharedPtr JsonObject); - FSatoriProperties(); // Default Constructor -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriSession.h b/Satori/Source/SatoriUnreal/Public/SatoriSession.h deleted file mode 100644 index 4c45e4aee..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriSession.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriUserSession.h" -#include "SatoriSession.generated.h" - -/** - * - */ -UCLASS(Blueprintable, BlueprintType) -class SATORIUNREAL_API USatoriSession : public UObject -{ - GENERATED_BODY() - -public: - - // Blueprint/C++ Exposed Struct with Data - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - FSatoriUserSession SessionData; - - static USatoriSession* SetupSession(const FString& AuthResponse); - - /** - * @return The authentication token used to construct this session. - */ - UFUNCTION(BlueprintPure, Category = "Satori|Authentication") - const FString GetAuthToken() const; - - /** - * @return The refresh token used to construct this session. - */ - UFUNCTION(BlueprintPure, Category = "Satori|Authentication") - const FString GetRefreshToken() const; - - /** - * @return The refresh properties used to construct this session. - */ - UFUNCTION(BlueprintPure, Category = "Satori|Authentication") - const FSatoriProperties GetProperties() const; - - /** - * Restore User Session - * - * @param Token Authentication Token from Session - * @param RefreshToken RefreshToken retrieved from Session - */ - UFUNCTION(BlueprintCallable, Category = "Satori|Authentication") - static USatoriSession* RestoreSession(FString Token, FString RefreshToken); - -private: - - USatoriSession() {} - - FString _AuthToken; - FString _RefreshToken; - FSatoriProperties _Properties; -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriUnreal.h b/Satori/Source/SatoriUnreal/Public/SatoriUnreal.h deleted file mode 100644 index cc30865f5..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriUnreal.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class FSatoriUnrealModule : public IModuleInterface -{ -public: - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; diff --git a/Satori/Source/SatoriUnreal/Public/SatoriUserSession.h b/Satori/Source/SatoriUnreal/Public/SatoriUserSession.h deleted file mode 100644 index 4e8b79b9f..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriUserSession.h +++ /dev/null @@ -1,41 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" -#include "SatoriProperties.h" -#include "SatoriUserSession.generated.h" - -USTRUCT(BlueprintType) -struct SATORIUNREAL_API FSatoriUserSession -{ - GENERATED_BODY() - - // The authentication token used to construct this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - FString AuthToken; - - // The refresh token used to construct this session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - FString RefreshToken; - - // Properties of this satori session. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Satori|Authentication") - FSatoriProperties Properties; - - FSatoriUserSession(); // Default Constructor -}; \ No newline at end of file diff --git a/Satori/Source/SatoriUnreal/Public/SatoriUtils.h b/Satori/Source/SatoriUnreal/Public/SatoriUtils.h deleted file mode 100644 index 76a7eb945..000000000 --- a/Satori/Source/SatoriUnreal/Public/SatoriUtils.h +++ /dev/null @@ -1,258 +0,0 @@ -/* -* Copyright 2025 The Nakama Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "CoreMinimal.h" - -#include "SatoriClient.h" -#include "Templates/SharedPointer.h" -#include "SatoriLogger.h" -#include "Misc/Base64.h" -#include "Serialization/JsonSerializer.h" -#include "SatoriLoggingMacros.h" - -class FJsonObject; - -class SATORIUNREAL_API FSatoriUtils -{ -public: - // Handle Request Methods for REST API - static FString ESatoriRequesMethodToFString(ESatoriRequestMethod Verb) - { - switch (Verb) - { - case ESatoriRequestMethod::GET: - return TEXT("GET"); - case ESatoriRequestMethod::POST: - return TEXT("POST"); - case ESatoriRequestMethod::PUT: - return TEXT("PUT"); - case ESatoriRequestMethod::DEL: - return TEXT("DELETE"); - default: - // Handle unrecognized verb if needed - break; - } - - // Return an empty string for unrecognized verbs - return FString(); - } - - // Bool to String Helper - static FString BoolToString(bool Value) - { - return Value ? TEXT("true") : TEXT("false"); - } - - // Build Query String - static FString BuildQueryString(const TMultiMap& QueryParams) - { - FString QueryString; - - for (const auto& Param : QueryParams) - { - if (!QueryString.IsEmpty()) - { - QueryString += "&"; - } - - // Only specific inputs needs to be encoded - //FString EncodedKey = FGenericPlatformHttp::UrlEncode(Param.Key); - //FString EncodedValue = FGenericPlatformHttp::UrlEncode(Param.Value); - - QueryString += Param.Key + "=" + Param.Value; - } - - return QueryString; - } - - // Extra client checks for lambdas in requests - static bool IsClientActive(const USatoriClient *Client) - { - return IsValid(Client) && Client->bIsActive == true; - } - - // Json helpers - static FString EncodeJson(TSharedPtr JsonObject) - { - FString OutputString; - const TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); - FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); - return OutputString; - } - - static bool SerializeJsonObject(const TSharedPtr& JsonObject, FString& OutSerializedJson) - { - if (!JsonObject.IsValid()) - { - return false; - } - - const TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&OutSerializedJson); - if (!FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter)) - { - JsonWriter->Close(); - return false; - } - - JsonWriter->Close(); - - return true; - } - - static TSharedPtr DeserializeJsonObject(const FString& JsonString) { - TSharedPtr JsonObject; - const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); - if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) - { - JsonObject = nullptr; - } - return JsonObject; - } - - static void AddVarsToJson(const TSharedPtr& JsonObject, const TMap& Vars, const FString varsFieldName = TEXT("vars"), const bool addAlways = false) { - - if (addAlways || Vars.Num() > 0) - { - const TSharedPtr VarsJson = MakeShared(); - for (const auto& Var : Vars) - { - if (!Var.Key.IsEmpty() && !Var.Value.IsEmpty()) - { - VarsJson->SetStringField(Var.Key, Var.Value); - } - else - { - SATORI_LOG_WARN(TEXT("AddVarsToJson: Empty key or value detected.")); - } - } - JsonObject->SetObjectField(varsFieldName, VarsJson); - } - } - - // Enum as integer string - template - static FString GetEnumValueAsIntString(TEnum EnumValue) - { - const int32 IntValue = static_cast(EnumValue); - return FString::FromInt(IntValue); - } - - // Error handling - static FSatoriError HandleInvalidClient() - { - FSatoriError Error; - Error.Message = FString(TEXT("Client Missing")); - Error.Code = ESatoriErrorCode::InvalidArgument; - - SATORI_LOG_ERROR(Error.Message); - - return Error; - } - - static FSatoriError HandleInvalidSession() - { - FSatoriError Error; - Error.Message = FString(TEXT("Session Missing")); - Error.Code = ESatoriErrorCode::InvalidArgument; - - SATORI_LOG_ERROR(Error.Message); - - return Error; - } - - static FSatoriError HandleInvalidClientAndSession() - { - FSatoriError Error; - Error.Message = FString(TEXT("Client and Session Missing")); - Error.Code = ESatoriErrorCode::InvalidArgument; - - SATORI_LOG_ERROR(Error.Message); - - return Error; - } - - // Base64 Encode/Decode - static FString Base64Encode(const FString& Source) - { - TArray ByteArray; - FTCHARToUTF8 StringSrc = FTCHARToUTF8(Source.GetCharArray().GetData()); - ByteArray.Append((uint8*)StringSrc.Get(), StringSrc.Length()); - - return FBase64::Encode(ByteArray); - } - - static bool Base64Decode(const FString& Source, FString& Dest) - { - TArray ByteArray; - bool Success = FBase64::Decode(Source, ByteArray); - - FUTF8ToTCHAR StringSrc = FUTF8ToTCHAR((const ANSICHAR*)ByteArray.GetData(), ByteArray.Num()); - Dest.AppendChars(StringSrc.Get(), StringSrc.Length()); - - return Success; - } - - // Working with Optionals (mainly from Blueprints) - - template - static TOptional CreateOptional(const T& value, const T& defaultValue) - { - return value != defaultValue ? TOptional(value) : TOptional(); - } - - // Conversion - - static TMap ConvertFloatMapToDouble(const TMap& FloatMap) - { - TMap DoubleMap; - for (const auto& Pair : FloatMap) - { - DoubleMap.Add(Pair.Key, static_cast(Pair.Value)); - } - return DoubleMap; - } - - // Common functions used by multiple clients - static void ProcessRequestComplete(FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess, const TFunction& SuccessCallback, const TFunction& ErrorCallback); - - static void HandleJsonSerializationFailure(TFunction ErrorCallback); - static bool IsSessionValid(const USatoriSession* Session, TFunction ErrorCallback); - static bool IsResponseSuccessful(int32 ResponseCode); - static FSatoriError CreateRequestFailureError(); - - // Make HTTP request - static TSharedRef MakeRequest( - const FString& URL, - const FString& Content, - ESatoriRequestMethod RequestMethod, - const FString& SessionToken, - float Timeout - ); - - static void SetBasicAuthorizationHeader(TSharedRef HttpRequest, const FString& ServerKey) - { - FString AuthToken = FString::Printf(TEXT("%s:"), *ServerKey); - FTCHARToUTF8 Utf8Token = FTCHARToUTF8(*AuthToken); - FString EncodedAuthToken = FBase64::Encode((const uint8*)Utf8Token.Get(), Utf8Token.Length()); - FString AuthorizationHeader = FString::Printf(TEXT("Basic %s"), *EncodedAuthToken); - - //SATORI_LOG_DEBUG(FString::Printf( TEXT("Authorization Header: %s"), *AuthorizationHeader )); - - HttpRequest->SetHeader(TEXT("Authorization"), AuthorizationHeader); - } -}; diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 000000000..c83b2ef16 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,357 @@ +version: '3' + +silent: true +dotenv: ['.env'] + +vars: + PROTO_FILE: "proto/nakama-types.proto" + PROTO_SERVICE_FILE: "proto/nakama-api.proto" + PROTO_RT_FILE: "proto/realtime.proto" + PROTO_SATORI_FILE: "proto/satori.proto" + CODEGEN: 'build/codegen{{if eq OS "windows"}}.exe{{end}}' + TESTRUNNER: 'build/testrunner{{if eq OS "windows"}}.exe{{end}}' + GITHUB_TOKEN: + sh: | + if [ -n "$GITHUB_TOKEN" ]; then + echo "$GITHUB_TOKEN" + else + creds=$(printf "protocol=https\nhost=github.com\n" | git credential fill) + for line in $creds; do + case $line in + password=*) echo "${line#password=}" ;; + esac + done + fi + +tasks: + codegen: + desc: 'Fetch protos, build codegen tool, and generate all Nakama + Satori modules.' + cmds: + - task: fetch-protos + - cmd: | + mkdir -p build + cd cmd/codegen && go build -o ../../{{.CODEGEN}} && cd ../.. + + mkdir -p \ + Nakama/Source/NakamaApi/Public \ + Nakama/Source/NakamaApi/Private \ + Nakama/Source/Nakama/Public \ + Nakama/Source/Nakama/Private \ + Nakama/Source/NakamaBlueprints/Public \ + Nakama/Source/NakamaBlueprints/Private \ + Satori/Source/SatoriApi/Public \ + Satori/Source/SatoriApi/Private \ + Satori/Source/Satori/Public \ + Satori/Source/Satori/Private \ + Satori/Source/SatoriBlueprints/Public \ + Satori/Source/SatoriBlueprints/Private \ + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-types.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + > Nakama/Source/NakamaApi/Public/NakamaTypes.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-types.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + > Nakama/Source/NakamaApi/Private/NakamaTypes.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rttypes.ue.h.tmpl \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/NakamaApi/Public/NakamaRtTypes.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rttypes.ue.cpp.tmpl \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/NakamaApi/Private/NakamaRtTypes.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-api.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/NakamaApi/Public/NakamaApi.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-api.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/NakamaApi/Private/NakamaApi.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/Nakama/Public/Nakama.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/Nakama/Private/Nakama.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rtclient.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/Nakama/Public/NakamaRt.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rtclient.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/Nakama/Private/NakamaRt.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-client-bplib.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/NakamaBlueprints/Public/NakamaClientBlueprintLibrary.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-client-bplib.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + > Nakama/Source/NakamaBlueprints/Private/NakamaClientBlueprintLibrary.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rtclient-bplib.ue.h.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/NakamaBlueprints/Public/NakamaRtClientBlueprintLibrary.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/nakama-rtclient-bplib.ue.cpp.tmpl \ + --proto {{.PROTO_FILE}} \ + --proto {{.PROTO_SERVICE_FILE}} \ + --proto {{.PROTO_RT_FILE}} \ + --prefix FNakamaRt \ + > Nakama/Source/NakamaBlueprints/Private/NakamaRtClientBlueprintLibrary.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori-api.ue.h.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/SatoriApi/Public/SatoriApi.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori-api.ue.cpp.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/SatoriApi/Private/SatoriApi.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori.ue.h.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/Satori/Public/Satori.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori.ue.cpp.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/Satori/Private/Satori.cpp + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori-client-bplib.ue.h.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/SatoriBlueprints/Public/SatoriClientBlueprintLibrary.h + + ./{{.CODEGEN}} \ + --template cmd/codegen/templates/satori-client-bplib.ue.cpp.tmpl \ + --proto {{.PROTO_SATORI_FILE}} \ + --prefix FSatori \ + > Satori/Source/SatoriBlueprints/Private/SatoriClientBlueprintLibrary.cpp + + start-server: + cmds: + - cmd: | + docker compose -f IntegrationTests/server/docker-compose.yml down + docker compose -f IntegrationTests/server/docker-compose.yml up --build -d + + build: + desc: 'Build the IntegrationTests editor target.' + deps: + - task: check-unreal-engine + cmds: + - cmd: | + PROJECT="$(pwd)/IntegrationTests/IntegrationTests.uproject" + + echo "Building IntegrationTestsEditor (Development)..." + "$UNREAL_ENGINE/Engine/Build/BatchFiles/Build.bat" \ + IntegrationTestsEditor Win64 Development \ + -Project="$PROJECT" + platforms: [windows] + - cmd: | + PROJECT="$(pwd)/IntegrationTests/IntegrationTests.uproject" + + echo "Building IntegrationTestsEditor (Development)..." + "$UNREAL_ENGINE/Engine/Build/BatchFiles/Build.sh" \ + IntegrationTestsEditor Mac Development \ + -Project="$PROJECT" \ + -Architecture_Mac=arm64 + platforms: [darwin] + - cmd: | + PROJECT="$(pwd)/IntegrationTests/IntegrationTests.uproject" + + echo "Building IntegrationTestsEditor (Development)..." + "$UNREAL_ENGINE/Engine/Build/BatchFiles/Build.sh" \ + IntegrationTestsEditor Linux Development \ + -Project="$PROJECT" + platforms: [linux] + + check-unreal-engine: + desc: 'Check or auto-detect UNREAL_ENGINE environment variable.' + cmds: + - cmd: | + if [ -z "$UNREAL_ENGINE" ]; then + echo "UNREAL_ENGINE not set, searching for Unreal Engine installations..." + + FOUND="" + # macOS: use mdfind (Spotlight) + if command -v mdfind &> /dev/null; then + CANDIDATE=$(mdfind -name "RunUAT.sh" 2>/dev/null | grep "Engine/Build/BatchFiles/RunUAT.sh" | head -1) + if [ -n "$CANDIDATE" ]; then + FOUND="${CANDIDATE%/Engine/Build/BatchFiles/RunUAT.sh}" + fi + # Linux: use locate + elif command -v locate &> /dev/null; then + CANDIDATE=$(locate "Engine/Build/BatchFiles/RunUAT.sh" 2>/dev/null | head -1) + if [ -n "$CANDIDATE" ]; then + FOUND="${CANDIDATE%/Engine/Build/BatchFiles/RunUAT.sh}" + fi + fi + + if [ -z "$FOUND" ]; then + echo "Error: Could not find Unreal Engine installation!" + echo "" + echo "Set UNREAL_ENGINE manually in .env file:" + echo " echo 'UNREAL_ENGINE=/path/to/UnrealEngine' >> .env" + exit 1 + fi + + echo "Found Unreal Engine at: $FOUND" + echo "Saving to .env file..." + echo "UNREAL_ENGINE=$FOUND" >> .env + export UNREAL_ENGINE="$FOUND" + fi + echo "UNREAL_ENGINE=$UNREAL_ENGINE" + platforms: [darwin, linux] + - cmd: | + powershell -NoProfile -Command "& { + if (-not \$env:UNREAL_ENGINE) { + Write-Host 'UNREAL_ENGINE not set, searching for Unreal Engine installations...' + + \$searchPaths = @( + 'C:\Program Files\Epic Games', + 'D:\Program Files\Epic Games', + 'E:\Program Files\Epic Games', + \"\$env:USERPROFILE\UnrealEngine\" + ) + + \$found = \$null + foreach (\$basePath in \$searchPaths) { + if (Test-Path \$basePath) { + \$candidates = Get-ChildItem -Path \$basePath -Directory -Filter 'UE_*' -ErrorAction SilentlyContinue | Sort-Object Name -Descending + foreach (\$candidate in \$candidates) { + \$uatPath = Join-Path \$candidate.FullName 'Engine\Build\BatchFiles\RunUAT.bat' + if (Test-Path \$uatPath) { + \$found = \$candidate.FullName + break + } + } + if (\$found) { break } + } + } + + if (-not \$found) { + Write-Host 'Error: Could not find Unreal Engine installation!' + Write-Host '' + Write-Host 'Please set UNREAL_ENGINE in .env file:' + Write-Host ' echo UNREAL_ENGINE=C:\Path\To\UE_5.x >> .env' + Write-Host '' + Write-Host 'Or pass it as an argument:' + Write-Host ' task test UNREAL_ENGINE=C:\Path\To\UE_5.x' + exit 1 + } + + Write-Host \"Found Unreal Engine at: \$found\" + Write-Host 'Saving to .env file...' + Add-Content -Path '.env' -Value \"UNREAL_ENGINE=\$found\" + \$env:UNREAL_ENGINE = \$found + } + Write-Host \"UNREAL_ENGINE=\$env:UNREAL_ENGINE\" + }" + platforms: [windows] + + test: + desc: 'Build and run IntegrationTests C++ automation suite.' + cmds: + - task: check-unreal-engine + - task: codegen + - cmd: | + if [ ! -d "IntegrationTests/satori/.git" ]; then + git clone git@github.com:heroiclabs/satori.git IntegrationTests/satori + git -C IntegrationTests/satori lfs pull + else + echo "Satori already cloned, skipping." + fi + if [ ! -d "IntegrationTests/dashboards/.git" ]; then + git clone git@github.com:heroiclabs/dashboards.git IntegrationTests/dashboards + else + echo "Dashboards already cloned, skipping." + fi + - task: start-server + - cmd: | + cmd /c "if exist IntegrationTests\Plugins\Nakama rmdir /s /q IntegrationTests\Plugins\Nakama" + cmd /c "mklink /J IntegrationTests\Plugins\Nakama Nakama" + cmd /c "if exist IntegrationTests\Plugins\Satori rmdir /s /q IntegrationTests\Plugins\Satori" + cmd /c "mklink /J IntegrationTests\Plugins\Satori Satori" + platforms: [windows] + - task: build + - cmd: | + cd cmd/testrunner && go build -o ../../{{.TESTRUNNER}} && cd ../.. + + ./{{.TESTRUNNER}} {{.FILTER}} + platforms: [windows] + - cmd: | + if [ -z "$UNREAL_ENGINE" ]; then + echo "Error: UNREAL_ENGINE is not set!" + echo "Set it in .env or pass it as an argument:" + echo " task test UNREAL_ENGINE=/path/to/engine" + exit 1 + fi + + cd cmd/testrunner && go build -o ../../{{.TESTRUNNER}} && cd ../.. + + ./{{.TESTRUNNER}} {{.FILTER}} + platforms: [darwin, linux] + + fetch-protos: + internal: true + vars: + PROTO_API_URL: 'https://raw.githubusercontent.com/heroiclabs/nakama-common/feat/flatten-proto-v2/api/api.proto' + PROTO_SERVICE_URL: 'https://raw.githubusercontent.com/heroiclabs/nakama/feat/flatten-proto/apigrpc/apigrpc.proto' + PROTO_RT_URL: 'https://raw.githubusercontent.com/heroiclabs/nakama-common/feat/flatten-proto-v2/rtapi/realtime.proto' + PROTO_SATORI_URL: 'https://api.github.com/repos/heroiclabs/satori/contents/api/satori.proto?ref=feat%2Freorder-proto' + cmds: + - cmd: | + mkdir -p proto + + curl -o {{.PROTO_FILE}} "{{.PROTO_API_URL}}" + curl -o {{.PROTO_SERVICE_FILE}} "{{.PROTO_SERVICE_URL}}" + curl -o {{.PROTO_RT_FILE}} "{{.PROTO_RT_URL}}" + + # Satori is private, so need credentials + curl -o "{{.PROTO_SATORI_FILE}}" \ + -H "Authorization: token {{.GITHUB_TOKEN}}" \ + -H "Accept: application/vnd.github.v3.raw" \ + "{{.PROTO_SATORI_URL}}" + diff --git a/cmake/Toolchain-Unreal-Linux.cmake b/cmake/Toolchain-Unreal-Linux.cmake deleted file mode 100644 index 5cea67a1e..000000000 --- a/cmake/Toolchain-Unreal-Linux.cmake +++ /dev/null @@ -1,8 +0,0 @@ -# see https://forums.unrealengine.com/t/compiling-libraries-in-linux/400275 -# see https://forums.unrealengine.com/t/ue4-linux-use-libcxx-0-fails-to-compile/478297 -# see https://forums.unrealengine.com/t/using-bundled-libc-in-thirdparty/427863 - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -set(CMAKE_CXX_FLAGS "-nostdinc++ -nodefaultlibs -I$ENV{UNREAL_ENGINE}/Engine/Source/ThirdParty/Unix/LibCxx/include/ -I$ENV{UNREAL_ENGINE}/Engine/Source/ThirdParty/Unix/LibCxx/include/c++/v1 ${CMAKE_CXX_FLAGS}") -set(CMAKE_CXX_STANDARD_LIBRARIES "-lpthread -lm -lc -lgcc_s -lgcc $ENV{UNREAL_ENGINE}/Engine/Source/ThirdParty/Unix/LibCxx/lib/Unix/x86_64-unknown-linux-gnu/libc++.a) diff --git a/cmd/codegen-modular/c89/resolve.go b/cmd/codegen-modular/c89/resolve.go new file mode 100644 index 000000000..ff965ddcf --- /dev/null +++ b/cmd/codegen-modular/c89/resolve.go @@ -0,0 +1,281 @@ +package c89 + +import ( + "fmt" + "strings" + + "github.com/golang-cz/textcase" + "heroiclabs.com/modular-codegen/schema" +) + +// MakeViewModelFactory returns a Production-compatible factory for C89 code generation. +// prefix is the type prefix (e.g., "Nk"), fnPrefix is the function prefix (e.g., "nk"). +func MakeViewModelFactory(prefix, fnPrefix, guardName string) func(any, schema.Api) (any, error) { + return func(tmRaw any, api schema.Api) (any, error) { + tm := tmRaw.(TypeMap) + return ViewModel{ + prefix: prefix, + fnPrefix: fnPrefix, + guardName: guardName, + messages: resolveMessages(api, tm, prefix), + enums: resolveEnums(api, prefix), + methods: resolveMethods(api, tm, prefix, fnPrefix), + rtOperations: resolveRtOperations(api, tm, prefix, fnPrefix), + }, nil + } +} + +func resolveField(name, protoType string, repeated bool, comment string, tm TypeMap, api schema.Api, prefix string) Field { + if idx := strings.LastIndex(protoType, "."); idx >= 0 { + stripped := protoType[idx+1:] + if _, ok := api.EnumsByName[stripped]; ok { + protoType = stripped + } else if _, ok := api.MessagesByName[stripped]; ok { + protoType = stripped + } + } + + f := Field{ + Name: toSnakeCase(name), + JsonName: name, + Comment: comment, + IsRepeated: repeated, + } + + if entry, ok := tm.Resolve(protoType); ok { + if repeated { + f.CType = entry.RepeatedType + "*" + f.ElemType = entry.RepeatedType + } else { + f.CType = entry.CType + } + f.IsValueType = entry.IsValueType + f.NeedsFree = entry.NeedsFree + f.FmtVerb = entry.FmtVerb + f.JsonCheck = entry.JsonCheck + f.JsonRead = entry.JsonRead + f.JsonAdd = entry.JsonAdd + f.JsonCast = entry.JsonCast + return f + } + + if _, ok := api.EnumsByName[protoType]; ok { + f.IsEnum = true + f.CType = fmt.Sprintf("%s%s", prefix, protoType) + f.IsValueType = true + f.FmtVerb = "%d" + f.JsonCheck = "cJSON_IsNumber" + f.JsonRead = fmt.Sprintf("(%s%s)(int)field->valuedouble", prefix, protoType) + f.JsonAdd = "cJSON_AddNumberToObject" + f.JsonCast = "(double)" + return f + } + + if _, ok := api.MessagesByName[protoType]; ok { + f.IsMessage = true + f.MsgType = fmt.Sprintf("%s%s", prefix, protoType) + f.CType = f.MsgType + "*" + f.ElemType = f.MsgType + return f + } + + f.CType = "const char*" + f.NeedsFree = true + f.FmtVerb = "%s" + f.JsonCheck = "cJSON_IsString" + f.JsonRead = "nk_strdup(field->valuestring)" + f.JsonAdd = "cJSON_AddStringToObject" + return f +} + +func resolveMapField(name, protoType, comment string, tm TypeMap, prefix string) Field { + f := Field{ + Name: toSnakeCase(name), + JsonName: name, + Comment: comment, + IsMap: true, + } + if entry, ok := tm.Resolve(protoType); ok { + if entry.CType == "const char*" { + f.CType = prefix + "StringMapEntry*" + } else { + f.CType = fmt.Sprintf("%sMapEntry_%s*", prefix, entry.CType) + } + } else { + f.CType = prefix + "StringMapEntry*" + } + return f +} + +func resolveMessages(api schema.Api, tm TypeMap, prefix string) []Message { + var result []Message + for _, msg := range api.Messages { + m := Message{Name: msg.Name, Comment: msg.Comment} + for _, f := range msg.Fields { + m.Fields = append(m.Fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix)) + } + for _, f := range msg.MapFields { + m.MapFields = append(m.MapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix)) + } + result = append(result, m) + } + return result +} + +func resolveEnums(api schema.Api, prefix string) []Enum { + var result []Enum + for _, enum := range api.Enums { + e := Enum{Name: enum.Name, Comment: enum.Comment} + for i, f := range enum.Fields { + e.Values = append(e.Values, EnumValue{ + Name: fmt.Sprintf("%s_%s_%s", strings.ToUpper(prefix), strings.ToUpper(toSnakeCase(enum.Name)), f.Name), + Value: f.Integer, + IsLast: i == len(enum.Fields)-1, + }) + } + result = append(result, e) + } + return result +} + +func resolveMethods(api schema.Api, tm TypeMap, prefix, fnPrefix string) []Method { + var result []Method + for _, rpc := range api.Rpcs { + result = append(result, resolveMethod(rpc, tm, api, prefix, fnPrefix)) + } + return result +} + +func resolveMethod(rpc *schema.VisitedRpc, tm TypeMap, api schema.Api, prefix, fnPrefix string) Method { + isAuth := strings.Contains(rpc.Name, "Authenticate") + isSessionRefresh := rpc.Name == "SessionRefresh" + + m := Method{ + Name: fnPrefix + "_" + toSnakeCase(textcase.PascalCase(rpc.Name)), + Comment: rpc.Comment, + HttpMethod: rpc.Method, + Endpoint: rpc.Endpoint, + NeedsAuth: !isAuth, + AuthType: "Bearer", + BodyMode: "none", + } + if isAuth || isSessionRefresh { + m.AuthType = "Basic" + } + + if rpc.ReturnType != nil { + m.HasReturn = true + m.ReturnType = rpc.ReturnType.Name + } + + if rpc.RequestType == nil { + return m + } + + pathSet := make(map[string]bool) + for _, p := range rpc.PathParams { + if p != "" { + pathSet[p] = true + } + } + + isGet := rpc.Method == "GET" + bodyField := rpc.BodyField + + for _, f := range rpc.RequestType.Fields { + rf := resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix) + switch { + case pathSet[f.Name]: + m.PathParams = append(m.PathParams, rf) + case isQueryParam(f.Name, bodyField, isGet): + m.QueryParams = append(m.QueryParams, rf) + m.AllParams = append(m.AllParams, rf) + default: + m.BodyParams = append(m.BodyParams, rf) + m.AllParams = append(m.AllParams, rf) + } + } + + for _, f := range rpc.RequestType.MapFields { + rf := resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix) + if bodyField == "*" && !isGet { + m.BodyParams = append(m.BodyParams, rf) + } else { + m.QueryParams = append(m.QueryParams, rf) + } + m.AllParams = append(m.AllParams, rf) + } + + switch { + case bodyField == "*" && !isGet: + m.BodyMode = "wildcard" + case bodyField != "" && bodyField != "*": + for _, f := range m.BodyParams { + if f.JsonName == bodyField { + m.BodyFieldName = f.Name + if f.IsMessage { + m.BodyMode = "object" + } else { + m.BodyMode = "scalar" + } + break + } + } + } + + return m +} + +func resolveRtOperations(api schema.Api, tm TypeMap, prefix, fnPrefix string) []RtOperation { + envelope, ok := api.MessagesByName["Envelope"] + if !ok { + return nil + } + + var result []RtOperation + for _, oneof := range envelope.OneofFields { + typeName := oneof.Name[strings.LastIndex(oneof.Name, ".")+1:] + msg, ok := api.MessagesByName[typeName] + if !ok { + continue + } + + op := RtOperation{ + CaseName: oneof.Name, + MessageName: msg.Name, + FuncName: fnPrefix + "_rt_" + toSnakeCase(textcase.PascalCase(msg.Name)), + Comment: msg.Comment, + } + + for _, f := range msg.Fields { + op.Fields = append(op.Fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix)) + } + for _, f := range msg.MapFields { + op.MapFields = append(op.MapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix)) + } + for _, f := range msg.OneofFields { + op.OneofFields = append(op.OneofFields, Field{ + Name: toSnakeCase(f.Name), + JsonName: f.Name, + CType: "const char*", + }) + } + + result = append(result, op) + } + return result +} + +func isQueryParam(jsonName, bodyField string, isGet bool) bool { + if isGet || bodyField == "" { + return true + } + if bodyField == "*" { + return false + } + return jsonName != bodyField +} + +func toSnakeCase(s string) string { + return strings.ToLower(textcase.SnakeCase(s)) +} diff --git a/cmd/codegen-modular/c89/templates.go b/cmd/codegen-modular/c89/templates.go new file mode 100644 index 000000000..a5430d663 --- /dev/null +++ b/cmd/codegen-modular/c89/templates.go @@ -0,0 +1,6 @@ +package c89 + +import "embed" + +//go:embed templates/*.tmpl +var Templates embed.FS diff --git a/cmd/codegen-modular/c89/templates/api.c.tmpl b/cmd/codegen-modular/c89/templates/api.c.tmpl new file mode 100644 index 000000000..4746499b8 --- /dev/null +++ b/cmd/codegen-modular/c89/templates/api.c.tmpl @@ -0,0 +1,186 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#include "{{ $.FnPrefix }}_api.h" +#include "cJSON.h" +#include +#include +#include + +/* Forward declaration: provided by the transport implementation. */ +extern void {{ $.FnPrefix }}_http_request( + {{ $.Prefix }}Client* client, + const char* method, + const char* url, + const char* auth_token, + const char* body_json, + void (*on_success)(cJSON* json, void* userdata), + void (*on_error)(const {{ $.Prefix }}Error* error, void* userdata), + void* userdata); + +static char* {{ $.FnPrefix }}_url_encode(const char* s) +{ + /* Minimal URL encoding — callers should replace with a proper implementation. */ + size_t len = strlen(s); + char* out = (char*)malloc(len * 3 + 1); + size_t j = 0; + size_t i; + for (i = 0; i < len; i++) + { + unsigned char c = (unsigned char)s[i]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') + out[j++] = (char)c; + else + { + sprintf(&out[j], "%%%02X", c); + j += 3; + } + } + out[j] = '\0'; + return out; +} +{{ range $m := $.Methods }} +/* {{ $m.Comment }} */ +{{ if $m.HasReturn }} +static void {{ $m.Name }}_on_success(cJSON* json, void* ctx) +{ + typedef struct { {{ $.Prefix }}{{ $m.ReturnType }}Callback cb; void* ud; } Ctx; + Ctx* c = (Ctx*)ctx; + {{ $.Prefix }}{{ $m.ReturnType }}* result = {{ $.FnPrefix }}_{{ toSnake $m.ReturnType }}_from_json(json); + if (c->cb) c->cb(result, c->ud); + {{ $.FnPrefix }}_{{ toSnake $m.ReturnType }}_destroy(result); + free(c); +} +{{ end }} +void {{ $m.Name }}( + {{ $.Prefix }}Client* client, +{{- if $m.NeedsAuth }} + const char* {{ if eq $m.AuthType "Basic" }}basic_auth{{ else }}bearer_token{{ end }}, +{{- end }} +{{- range $m.PathParams }} + const char* {{ .Name }}, +{{- end }} +{{- range $m.AllParams }} +{{- if .IsRepeated }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- else if .IsMap }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- else }} + {{ .CType }} {{ .Name }}, +{{- end }} +{{- end }} +{{- if $m.HasReturn }} + {{ $.Prefix }}{{ $m.ReturnType }}Callback on_success, +{{- else }} + {{ $.Prefix }}VoidCallback on_success, +{{- end }} + {{ $.Prefix }}ErrorCallback on_error, + void* userdata) +{ + char url[2048]; + char* qbuf; + int qlen = 0; + char query[4096]; + query[0] = '\0'; + + snprintf(url, sizeof(url), "%s", "{{ $m.Endpoint }}"); +{{- range $m.PathParams }} + { + char* enc = {{ $.FnPrefix }}_url_encode({{ .Name }}); + char tmp[2048]; + snprintf(tmp, sizeof(tmp), "%s", url); + { char* pos = strstr(tmp, "{{ printf "{%s}" .JsonName }}"); if (pos) { size_t before = (size_t)(pos - tmp); memcpy(url, tmp, before); strcpy(url + before, enc); strcat(url, pos + {{ printf "%d" (len (printf "{%s}" .JsonName)) }}); } } + free(enc); + } +{{- end }} +{{- if $m.QueryParams }} + + qbuf = query; +{{- range $m.QueryParams }} +{{- if .IsRepeated }} + { int i; for (i = 0; i < {{ .Name }}_count; i++) { + char* enc = {{ $.FnPrefix }}_url_encode({{ .Name }}[i]); + qlen += snprintf(qbuf + qlen, sizeof(query) - qlen, "%s{{ .JsonName }}=%s", qlen > 0 ? "&" : "", enc); + free(enc); + }} +{{- else if .NeedsFree }} + if ({{ .Name }}) { char* enc = {{ $.FnPrefix }}_url_encode({{ .Name }}); qlen += snprintf(qbuf + qlen, sizeof(query) - qlen, "%s{{ .JsonName }}=%s", qlen > 0 ? "&" : "", enc); free(enc); } +{{- else if .IsValueType }} + if ({{ .Name }}) qlen += snprintf(qbuf + qlen, sizeof(query) - qlen, "%s{{ .JsonName }}={{ .FmtVerb }}", qlen > 0 ? "&" : "", {{ .Name }}); +{{- end }} +{{- end }} + if (qlen > 0) { strcat(url, "?"); strcat(url, query); } +{{- end }} + +{{- if eq $m.BodyMode "wildcard" }} + { + cJSON* body = cJSON_CreateObject(); +{{- range $m.BodyParams }} +{{- if .IsMessage }} + if ({{ .Name }}) cJSON_AddItemToObject(body, "{{ .JsonName }}", {{ $.FnPrefix }}_{{ toSnake .MsgType }}_to_json({{ .Name }})); +{{- else if .NeedsFree }} + if ({{ .Name }}) cJSON_AddStringToObject(body, "{{ .JsonName }}", {{ .Name }}); +{{- else if ne .JsonAdd "" }} + {{ .JsonAdd }}(body, "{{ .JsonName }}", {{ .JsonCast }}{{ .Name }}); +{{- end }} +{{- end }} + { + char* body_str = cJSON_PrintUnformatted(body); +{{- else if eq $m.BodyMode "object" }} + { + cJSON* body = {{ $.FnPrefix }}_{{ toSnake $m.BodyFieldName }}_to_json({{ toSnake $m.BodyFieldName }}); + { + char* body_str = cJSON_PrintUnformatted(body); +{{- else }} + { + { + const char* body_str = NULL; +{{- end }} + +{{- if $m.HasReturn }} + { + typedef struct { {{ $.Prefix }}{{ $m.ReturnType }}Callback cb; void* ud; } Ctx; + Ctx* ctx = (Ctx*)malloc(sizeof(Ctx)); + ctx->cb = on_success; + ctx->ud = userdata; + {{ $.FnPrefix }}_http_request(client, "{{ $m.HttpMethod }}", url, + {{ if $m.NeedsAuth }}{{ if eq $m.AuthType "Basic" }}basic_auth{{ else }}bearer_token{{ end }}{{ else }}NULL{{ end }}, + body_str, + {{ $m.Name }}_on_success, + (void (*)(const {{ $.Prefix }}Error*, void*))on_error, + ctx); + } +{{- else }} + {{ $.FnPrefix }}_http_request(client, "{{ $m.HttpMethod }}", url, + {{ if $m.NeedsAuth }}{{ if eq $m.AuthType "Basic" }}basic_auth{{ else }}bearer_token{{ end }}{{ else }}NULL{{ end }}, + body_str, + (void (*)(cJSON*, void*))on_success, + (void (*)(const {{ $.Prefix }}Error*, void*))on_error, + userdata); +{{- end }} +{{- if or (eq $m.BodyMode "wildcard") (eq $m.BodyMode "object") }} + free(body_str); + } + cJSON_Delete(body); + } +{{- else }} + } + } +{{- end }} +} +{{ end }} diff --git a/cmd/codegen-modular/c89/templates/api.h.tmpl b/cmd/codegen-modular/c89/templates/api.h.tmpl new file mode 100644 index 000000000..f643fb459 --- /dev/null +++ b/cmd/codegen-modular/c89/templates/api.h.tmpl @@ -0,0 +1,75 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#ifndef {{ $.GuardName }}_API_H +#define {{ $.GuardName }}_API_H + +#include "{{ $.FnPrefix }}_types.h" +#include "{{ $.FnPrefix }}_enums.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque client handle. Provided by the transport implementation. */ +typedef struct {{ $.Prefix }}Client {{ $.Prefix }}Client; + +/* Error information passed to error callbacks. */ +typedef struct {{ $.Prefix }}Error { + int code; + const char* message; +} {{ $.Prefix }}Error; + +/* Callback types. */ +typedef void (*{{ $.Prefix }}VoidCallback)(void* userdata); +typedef void (*{{ $.Prefix }}ErrorCallback)(const {{ $.Prefix }}Error* error, void* userdata); +{{- range $.UniqueReturnTypes }} +typedef void (*{{ $.Prefix }}{{ . }}Callback)(const {{ $.Prefix }}{{ . }}* result, void* userdata); +{{- end }} +{{ range $m := $.Methods }} +/* {{ $m.Comment }} */ +void {{ $m.Name }}( + {{ $.Prefix }}Client* client, +{{- if $m.NeedsAuth }} + const char* {{ if eq $m.AuthType "Basic" }}basic_auth{{ else }}bearer_token{{ end }}, +{{- end }} +{{- range $m.PathParams }} + const char* {{ .Name }}, +{{- end }} +{{- range $m.AllParams }} +{{- if .IsRepeated }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- else if .IsMap }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- else }} + {{ .CType }} {{ .Name }}, +{{- end }} +{{- end }} +{{- if $m.HasReturn }} + {{ $.Prefix }}{{ $m.ReturnType }}Callback on_success, +{{- else }} + {{ $.Prefix }}VoidCallback on_success, +{{- end }} + {{ $.Prefix }}ErrorCallback on_error, + void* userdata); +{{ end }} +#ifdef __cplusplus +} +#endif + +#endif /* {{ $.GuardName }}_API_H */ diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NPlatformParams.h b/cmd/codegen-modular/c89/templates/enums.h.tmpl similarity index 52% rename from Nakama/Source/NakamaCore/Public/nakama-cpp/NPlatformParams.h rename to cmd/codegen-modular/c89/templates/enums.h.tmpl index 18e3c873c..4115d9b64 100644 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NPlatformParams.h +++ b/cmd/codegen-modular/c89/templates/enums.h.tmpl @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Nakama Authors + * Copyright {{currentYear}} The Nakama Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,24 @@ * limitations under the License. */ -#pragma once +/* Auto-generated by modular-codegen. DO NOT EDIT. */ -#include +#ifndef {{ $.GuardName }}_ENUMS_H +#define {{ $.GuardName }}_ENUMS_H -#ifdef __ANDROID__ -#include "jni.h" +#ifdef __cplusplus +extern "C" { +#endif +{{ range $enum := $.Enums }} +/* {{ $enum.Comment }} */ +typedef enum {{ $.Prefix }}{{ $enum.Name }} { +{{- range $enum.Values }} + {{ .Name }} = {{ .Value }}{{ if not .IsLast }},{{ end }} +{{- end }} +} {{ $.Prefix }}{{ $enum.Name }}; +{{ end }} +#ifdef __cplusplus +} #endif -NAKAMA_NAMESPACE_BEGIN - -// Keeping for API stability in the case that we need to add platform-specific features. -struct NPlatformParameters {}; - -NAKAMA_NAMESPACE_END +#endif /* {{ $.GuardName }}_ENUMS_H */ diff --git a/cmd/codegen-modular/c89/templates/rt-client.h.tmpl b/cmd/codegen-modular/c89/templates/rt-client.h.tmpl new file mode 100644 index 000000000..d5ee8beb5 --- /dev/null +++ b/cmd/codegen-modular/c89/templates/rt-client.h.tmpl @@ -0,0 +1,64 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#ifndef {{ $.GuardName }}_RT_CLIENT_H +#define {{ $.GuardName }}_RT_CLIENT_H + +#include "{{ $.FnPrefix }}_rt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque realtime client handle. Provided by the WebSocket transport implementation. */ +typedef struct {{ $.Prefix }}RtClient {{ $.Prefix }}RtClient; + +/* Realtime response. */ +typedef struct {{ $.Prefix }}RtResponse { + int error_code; /* 0 on success */ + const char* error_message; /* NULL on success */ + const char* json_data; /* raw JSON payload, NULL on error */ +} {{ $.Prefix }}RtResponse; + +/* Callback for realtime operations. */ +typedef void (*{{ $.Prefix }}RtCallback)(const {{ $.Prefix }}RtResponse* response, void* userdata); +{{ range $op := $.RtOperations }} +/* {{ $op.Comment }} */ +void {{ $op.FuncName }}( + {{ $.Prefix }}RtClient* client, +{{- range $op.OneofFields }} + const char* {{ .Name }}, +{{- end }} +{{- range $op.Fields }} +{{- if .IsRepeated }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- else }} + {{ .CType }} {{ .Name }}, +{{- end }} +{{- end }} +{{- range $op.MapFields }} + {{ .CType }} {{ .Name }}, int {{ .Name }}_count, +{{- end }} + {{ $.Prefix }}RtCallback callback, + void* userdata); +{{ end }} +#ifdef __cplusplus +} +#endif + +#endif /* {{ $.GuardName }}_RT_CLIENT_H */ diff --git a/cmd/codegen-modular/c89/templates/rt-types.h.tmpl b/cmd/codegen-modular/c89/templates/rt-types.h.tmpl new file mode 100644 index 000000000..74772c36f --- /dev/null +++ b/cmd/codegen-modular/c89/templates/rt-types.h.tmpl @@ -0,0 +1,56 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#ifndef {{ $.GuardName }}_RT_TYPES_H +#define {{ $.GuardName }}_RT_TYPES_H + +#include "{{ $.FnPrefix }}_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declarations. */ +{{- range $.RtOperations }} +typedef struct {{ $.Prefix }}Rt{{ .MessageName }} {{ $.Prefix }}Rt{{ .MessageName }}; +{{- end }} +{{ range $op := $.RtOperations }} +/* {{ $op.Comment }} */ +struct {{ $.Prefix }}Rt{{ $op.MessageName }} { +{{- range $op.OneofFields }} + {{ .CType }} {{ .Name }}; +{{- end }} +{{- range $op.Fields }} +{{- if .IsRepeated }} + {{ .CType }} {{ .Name }}; + int {{ .Name }}_count; +{{- else }} + {{ .CType }} {{ .Name }}; /* {{ .Comment }} */ +{{- end }} +{{- end }} +{{- range $op.MapFields }} + {{ .CType }} {{ .Name }}; + int {{ .Name }}_count; +{{- end }} +}; +{{ end }} +#ifdef __cplusplus +} +#endif + +#endif /* {{ $.GuardName }}_RT_TYPES_H */ diff --git a/cmd/codegen-modular/c89/templates/types.c.tmpl b/cmd/codegen-modular/c89/templates/types.c.tmpl new file mode 100644 index 000000000..3f397d504 --- /dev/null +++ b/cmd/codegen-modular/c89/templates/types.c.tmpl @@ -0,0 +1,176 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#include "{{ $.FnPrefix }}_types.h" +#include "cJSON.h" +#include +#include + +static char* nk_strdup(const char* s) +{ + size_t len; + char* copy; + if (!s) return NULL; + len = strlen(s) + 1; + copy = (char*)malloc(len); + if (copy) memcpy(copy, s, len); + return copy; +} +{{ range $msg := $.Messages }} +/* --- {{ $.Prefix }}{{ $msg.Name }} --- */ + +{{ $.Prefix }}{{ $msg.Name }}* {{ $.FnPrefix }}_{{ toSnake $msg.Name }}_create(void) +{ + return ({{ $.Prefix }}{{ $msg.Name }}*)calloc(1, sizeof({{ $.Prefix }}{{ $msg.Name }})); +} + +void {{ $.FnPrefix }}_{{ toSnake $msg.Name }}_destroy({{ $.Prefix }}{{ $msg.Name }}* p) +{ + if (!p) return; +{{- range $msg.Fields }} +{{- if .IsRepeated }} +{{- if .IsMessage }} + { int i; for (i = 0; i < p->{{ .Name }}_count; i++) {{ $.FnPrefix }}_{{ toSnake .ElemType }}_destroy(&p->{{ .Name }}[i]); } +{{- else if .NeedsFree }} + { int i; for (i = 0; i < p->{{ .Name }}_count; i++) free((void*)p->{{ .Name }}[i]); } +{{- end }} + free(p->{{ .Name }}); +{{- else if .IsMessage }} + if (p->{{ .Name }}) {{ $.FnPrefix }}_{{ toSnake .MsgType }}_destroy(p->{{ .Name }}); +{{- else if .NeedsFree }} + free((void*)p->{{ .Name }}); +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + { int i; for (i = 0; i < p->{{ .Name }}_count; i++) { free((void*)p->{{ .Name }}[i].key); free((void*)p->{{ .Name }}[i].value); } } + free(p->{{ .Name }}); +{{- end }} + free(p); +} + +{{ $.Prefix }}{{ $msg.Name }}* {{ $.FnPrefix }}_{{ toSnake $msg.Name }}_from_json(cJSON* json) +{ + {{ $.Prefix }}{{ $msg.Name }}* obj = {{ $.FnPrefix }}_{{ toSnake $msg.Name }}_create(); + cJSON* field; + if (!json) return obj; +{{- range $msg.Fields }} +{{- if .IsRepeated }} + field = cJSON_GetObjectItemCaseSensitive(json, "{{ .JsonName }}"); + if (cJSON_IsArray(field)) + { + int count = cJSON_GetArraySize(field); +{{- if .IsMessage }} + obj->{{ .Name }} = ({{ .ElemType }}*)calloc(count, sizeof({{ .ElemType }})); +{{- else }} + obj->{{ .Name }} = ({{ .ElemType }}*)calloc(count, sizeof({{ .ElemType }})); +{{- end }} + obj->{{ .Name }}_count = count; + { + int i = 0; + cJSON* item; + cJSON_ArrayForEach(item, field) + { +{{- if .IsMessage }} + {{ .ElemType }}* child = {{ $.FnPrefix }}_{{ toSnake .ElemType }}_from_json(item); + if (child) { obj->{{ .Name }}[i] = *child; free(child); } +{{- else if .NeedsFree }} + if (cJSON_IsString(item)) obj->{{ .Name }}[i] = nk_strdup(item->valuestring); +{{- else }} + if (cJSON_IsNumber(item)) obj->{{ .Name }}[i] = ({{ .ElemType }})item->valuedouble; +{{- end }} + i++; + } + } + } +{{- else if .IsMessage }} + field = cJSON_GetObjectItemCaseSensitive(json, "{{ .JsonName }}"); + if (cJSON_IsObject(field)) obj->{{ .Name }} = {{ $.FnPrefix }}_{{ toSnake .MsgType }}_from_json(field); +{{- else if .IsEnum }} + field = cJSON_GetObjectItemCaseSensitive(json, "{{ .JsonName }}"); + if ({{ .JsonCheck }}(field)) obj->{{ .Name }} = {{ .JsonRead }}; +{{- else if ne .JsonCheck "" }} + field = cJSON_GetObjectItemCaseSensitive(json, "{{ .JsonName }}"); + if ({{ .JsonCheck }}(field)) obj->{{ .Name }} = {{ .JsonRead }}; +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + field = cJSON_GetObjectItemCaseSensitive(json, "{{ .JsonName }}"); + if (cJSON_IsObject(field)) + { + int count = cJSON_GetArraySize(field); + obj->{{ .Name }} = ({{ $.Prefix }}StringMapEntry*)calloc(count, sizeof({{ $.Prefix }}StringMapEntry)); + obj->{{ .Name }}_count = count; + { + int i = 0; + cJSON* item; + cJSON_ArrayForEach(item, field) + { + obj->{{ .Name }}[i].key = nk_strdup(item->string); + if (cJSON_IsString(item)) obj->{{ .Name }}[i].value = nk_strdup(item->valuestring); + i++; + } + } + } +{{- end }} + return obj; +} + +cJSON* {{ $.FnPrefix }}_{{ toSnake $msg.Name }}_to_json(const {{ $.Prefix }}{{ $msg.Name }}* obj) +{ + cJSON* json = cJSON_CreateObject(); + if (!obj) return json; +{{- range $msg.Fields }} +{{- if .IsRepeated }} + if (obj->{{ .Name }}_count > 0) + { + cJSON* arr = cJSON_AddArrayToObject(json, "{{ .JsonName }}"); + { int i; for (i = 0; i < obj->{{ .Name }}_count; i++) + { +{{- if .IsMessage }} + cJSON_AddItemToArray(arr, {{ $.FnPrefix }}_{{ toSnake .ElemType }}_to_json(&obj->{{ .Name }}[i])); +{{- else if .NeedsFree }} + if (obj->{{ .Name }}[i]) cJSON_AddItemToArray(arr, cJSON_CreateString(obj->{{ .Name }}[i])); +{{- else }} + cJSON_AddItemToArray(arr, cJSON_CreateNumber({{ .JsonCast }}obj->{{ .Name }}[i])); +{{- end }} + }} + } +{{- else if .IsMessage }} + if (obj->{{ .Name }}) cJSON_AddItemToObject(json, "{{ .JsonName }}", {{ $.FnPrefix }}_{{ toSnake .MsgType }}_to_json(obj->{{ .Name }})); +{{- else if .IsEnum }} + {{ .JsonAdd }}(json, "{{ .JsonName }}", {{ .JsonCast }}(int)obj->{{ .Name }}); +{{- else if .NeedsFree }} + if (obj->{{ .Name }}) {{ .JsonAdd }}(json, "{{ .JsonName }}", obj->{{ .Name }}); +{{- else if ne .JsonAdd "" }} + {{ .JsonAdd }}(json, "{{ .JsonName }}", {{ .JsonCast }}obj->{{ .Name }}); +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + if (obj->{{ .Name }}_count > 0) + { + cJSON* map = cJSON_AddObjectToObject(json, "{{ .JsonName }}"); + { int i; for (i = 0; i < obj->{{ .Name }}_count; i++) + { + if (obj->{{ .Name }}[i].key && obj->{{ .Name }}[i].value) + cJSON_AddStringToObject(map, obj->{{ .Name }}[i].key, obj->{{ .Name }}[i].value); + }} + } +{{- end }} + return json; +} +{{ end }} diff --git a/cmd/codegen-modular/c89/templates/types.h.tmpl b/cmd/codegen-modular/c89/templates/types.h.tmpl new file mode 100644 index 000000000..11aa053b3 --- /dev/null +++ b/cmd/codegen-modular/c89/templates/types.h.tmpl @@ -0,0 +1,69 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Auto-generated by modular-codegen. DO NOT EDIT. */ + +#ifndef {{ $.GuardName }}_TYPES_H +#define {{ $.GuardName }}_TYPES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Key-value pair for map fields. */ +typedef struct {{ $.Prefix }}StringMapEntry { + const char* key; + const char* value; +} {{ $.Prefix }}StringMapEntry; + +/* Forward declarations. */ +{{- range $.Messages }} +typedef struct {{ $.Prefix }}{{ .Name }} {{ $.Prefix }}{{ .Name }}; +{{- end }} +{{ range $msg := $.Messages }} +/* {{ $msg.Comment }} */ +struct {{ $.Prefix }}{{ $msg.Name }} { +{{- range $msg.Fields }} +{{- if .IsRepeated }} + {{ .CType }} {{ .Name }}; /* {{ .Comment }} */ + int {{ .Name }}_count; +{{- else }} + {{ .CType }} {{ .Name }}; /* {{ .Comment }} */ +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + {{ .CType }} {{ .Name }}; /* {{ .Comment }} */ + int {{ .Name }}_count; +{{- end }} +}; +{{ end }} +/* Lifecycle: allocate a zero-initialized struct. Free with {{ $.FnPrefix }}_destroy(). */ +{{- range $.Messages }} +{{ $.Prefix }}{{ .Name }}* {{ $.FnPrefix }}_{{ toSnake .Name }}_create(void); +{{- end }} + +/* Lifecycle: deep-free a struct and all owned sub-fields. NULL-safe. */ +{{- range $.Messages }} +void {{ $.FnPrefix }}_{{ toSnake .Name }}_destroy({{ $.Prefix }}{{ .Name }}* p); +{{- end }} + +#ifdef __cplusplus +} +#endif + +#endif /* {{ $.GuardName }}_TYPES_H */ diff --git a/cmd/codegen-modular/c89/typemap.go b/cmd/codegen-modular/c89/typemap.go new file mode 100644 index 000000000..6af1812d3 --- /dev/null +++ b/cmd/codegen-modular/c89/typemap.go @@ -0,0 +1,101 @@ +package c89 + +// TypeEntry maps a proto primitive to its C89 representation. +type TypeEntry struct { + CType string // Scalar: "const char*", "int32_t", "int", "double" + RepeatedType string // Array element type: "const char*", "int32_t" + MapType string // Map value type (key is always const char*): "const char*", "int32_t" + IsValueType bool // False for const char* (pointer), true for int32_t, int, etc. + FmtVerb string // printf verb for query strings: "%s", "%d", "%lld" + + // cJSON serialization + JsonCheck string // cJSON type check: "cJSON_IsString", "cJSON_IsNumber", "cJSON_IsBool" + JsonRead string // Expression to read value from cJSON*: "nk_strdup(field->valuestring)" + JsonAdd string // cJSON add function: "cJSON_AddStringToObject", "cJSON_AddNumberToObject" + JsonCast string // Cast for to_json number arg: "(double)", "" for string/bool + NeedsFree bool // Whether destroy should free this field (strings) +} + +type TypeMap interface { + Resolve(protoType string) (*TypeEntry, bool) +} + +type CTypeMap struct { + entries map[string]*TypeEntry +} + +func NewCTypeMap() *CTypeMap { + str := &TypeEntry{ + CType: "const char*", RepeatedType: "const char*", MapType: "const char*", + FmtVerb: "%s", NeedsFree: true, + JsonCheck: "cJSON_IsString", JsonRead: "nk_strdup(field->valuestring)", + JsonAdd: "cJSON_AddStringToObject", JsonCast: "", + } + boolean := &TypeEntry{ + CType: "int", RepeatedType: "int", MapType: "int", + IsValueType: true, FmtVerb: "%d", + JsonCheck: "cJSON_IsBool", JsonRead: "cJSON_IsTrue(field)", + JsonAdd: "cJSON_AddBoolToObject", JsonCast: "", + } + i32 := &TypeEntry{ + CType: "int32_t", RepeatedType: "int32_t", MapType: "int32_t", + IsValueType: true, FmtVerb: "%d", + JsonCheck: "cJSON_IsNumber", JsonRead: "(int32_t)field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "(double)", + } + u32 := &TypeEntry{ + CType: "uint32_t", RepeatedType: "uint32_t", MapType: "uint32_t", + IsValueType: true, FmtVerb: "%u", + JsonCheck: "cJSON_IsNumber", JsonRead: "(uint32_t)field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "(double)", + } + i64 := &TypeEntry{ + CType: "int64_t", RepeatedType: "int64_t", MapType: "int64_t", + IsValueType: true, FmtVerb: "%lld", + JsonCheck: "cJSON_IsNumber", JsonRead: "(int64_t)field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "(double)", + } + u64 := &TypeEntry{ + CType: "uint64_t", RepeatedType: "uint64_t", MapType: "uint64_t", + IsValueType: true, FmtVerb: "%llu", + JsonCheck: "cJSON_IsNumber", JsonRead: "(uint64_t)field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "(double)", + } + f32 := &TypeEntry{ + CType: "float", RepeatedType: "float", MapType: "float", + IsValueType: true, FmtVerb: "%f", + JsonCheck: "cJSON_IsNumber", JsonRead: "(float)field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "(double)", + } + f64 := &TypeEntry{ + CType: "double", RepeatedType: "double", MapType: "double", + IsValueType: true, FmtVerb: "%f", + JsonCheck: "cJSON_IsNumber", JsonRead: "field->valuedouble", + JsonAdd: "cJSON_AddNumberToObject", JsonCast: "", + } + bytes := &TypeEntry{ + CType: "const char*", RepeatedType: "const char*", MapType: "const char*", + FmtVerb: "%s", NeedsFree: true, + JsonCheck: "cJSON_IsString", JsonRead: "nk_strdup(field->valuestring)", + JsonAdd: "cJSON_AddStringToObject", JsonCast: "", + } + + return &CTypeMap{ + entries: map[string]*TypeEntry{ + "string": str, "google.protobuf.StringValue": str, "google.protobuf.Timestamp": str, + "bool": boolean, "google.protobuf.BoolValue": boolean, + "int32": i32, "google.protobuf.Int32Value": i32, + "uint32": u32, "google.protobuf.UInt32Value": u32, + "int64": i64, "google.protobuf.Int64Value": i64, + "uint64": u64, "google.protobuf.UInt64Value": u64, + "float": f32, "google.protobuf.FloatValue": f32, + "double": f64, "google.protobuf.DoubleValue": f64, + "bytes": bytes, "google.protobuf.BytesValue": bytes, + }, + } +} + +func (m *CTypeMap) Resolve(protoType string) (*TypeEntry, bool) { + e, ok := m.entries[protoType] + return e, ok +} diff --git a/cmd/codegen-modular/c89/viewmodel.go b/cmd/codegen-modular/c89/viewmodel.go new file mode 100644 index 000000000..d5949241f --- /dev/null +++ b/cmd/codegen-modular/c89/viewmodel.go @@ -0,0 +1,111 @@ +package c89 + +// Field is a resolved proto field with C89 type metadata. +type Field struct { + Name string /* snake_case field/param name */ + JsonName string /* original proto name */ + Comment string + CType string /* "const char*", "int32_t", "NkUser*" */ + ElemType string /* element type for repeated: "int32_t", "NkUser" (no pointer) */ + IsRepeated bool /* has companion _count member */ + IsMap bool /* NkStringMapEntry* with _count */ + IsMessage bool /* pointer to nested struct */ + IsEnum bool + IsValueType bool /* true for int32_t, int, etc.; false for const char* */ + NeedsFree bool /* true for const char* fields (need free in destroy) */ + FmtVerb string /* printf verb for query params */ + + /* cJSON serialization */ + JsonCheck string /* "cJSON_IsString", "cJSON_IsNumber", "cJSON_IsBool" */ + JsonRead string /* "nk_strdup(field->valuestring)", "(int32_t)field->valuedouble" */ + JsonAdd string /* "cJSON_AddStringToObject", "cJSON_AddNumberToObject" */ + JsonCast string /* cast for to_json: "(double)", "" */ + MsgType string /* for message fields: "NkUser" (without pointer) */ +} + +// Message is a resolved proto message. +type Message struct { + Name string /* PascalCase without prefix */ + Comment string + Fields []Field + MapFields []Field +} + +// EnumValue is a single enum constant. +type EnumValue struct { + Name string + Value int + IsLast bool +} + +// Enum is a resolved proto enum. +type Enum struct { + Name string + Comment string + Values []EnumValue +} + +// Method is a resolved RPC. +type Method struct { + Name string /* snake_case function name */ + Comment string + HttpMethod string + Endpoint string + + AuthType string /* "Bearer", "Basic" */ + NeedsAuth bool + + PathParams []Field + QueryParams []Field + BodyParams []Field + AllParams []Field + + BodyMode string /* "none", "wildcard", "object", "scalar" */ + BodyFieldName string + + HasReturn bool + ReturnType string /* PascalCase message name */ +} + +// RtOperation is a resolved realtime WebSocket operation. +type RtOperation struct { + CaseName string /* The oneof case name */ + MessageName string /* PascalCase type name */ + FuncName string /* snake_case function name */ + Comment string + Fields []Field + MapFields []Field + OneofFields []Field +} + +// ViewModel is the top-level data object for C89 templates. +type ViewModel struct { + prefix string /* "Nk" — type prefix */ + fnPrefix string /* "nk" — function prefix */ + guardName string /* "NK" — include guard prefix */ + messages []Message + enums []Enum + methods []Method + rtOperations []RtOperation +} + +func (vm ViewModel) Prefix() string { return vm.prefix } +func (vm ViewModel) FnPrefix() string { return vm.fnPrefix } +func (vm ViewModel) GuardName() string { return vm.guardName } +func (vm ViewModel) Messages() []Message { return vm.messages } +func (vm ViewModel) Enums() []Enum { return vm.enums } +func (vm ViewModel) Methods() []Method { return vm.methods } +func (vm ViewModel) RtOperations() []RtOperation { return vm.rtOperations } + +// UniqueReturnTypes returns deduplicated return type names across all methods. +func (vm ViewModel) UniqueReturnTypes() []string { + seen := make(map[string]bool) + var result []string + for _, m := range vm.methods { + if m.HasReturn && !seen[m.ReturnType] { + seen[m.ReturnType] = true + result = append(result, m.ReturnType) + } + } + return result +} diff --git a/cmd/codegen-modular/codegen/module.go b/cmd/codegen-modular/codegen/module.go new file mode 100644 index 000000000..517df1e53 --- /dev/null +++ b/cmd/codegen-modular/codegen/module.go @@ -0,0 +1,113 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +// Package codegen is a target-agnostic template orchestration engine. +// It knows how to parse templates, invoke view-model factories, and write +// output files — but has no knowledge of any specific target language. +// All target-specific types (type maps, resolved fields, view models) +// live in their respective target packages (e.g., unreal). +package codegen + +import ( + "embed" + "fmt" + "os" + "path/filepath" + "text/template" + + "heroiclabs.com/modular-codegen/schema" +) + +// StaticFile maps an embedded file to its output name. +type StaticFile struct { + FS embed.FS // Embedded filesystem containing the file. + Source string // Path within the embedded FS (e.g., "static/NakamaHttpHelper.h"). + Output string // Output file name (e.g., "NakamaHttpHelper.h"). +} + +type Production struct { + TemplateContent string // Template source text (not a file path) + FuncMap template.FuncMap + // ViewModelFactory builds the data object passed to the template. + // Templates call methods on the concrete type via reflection, so the + // factory can return any type — there is no shared interface constraint. + // The first argument is the Module's TypeMap (opaque to codegen). + ViewModelFactory func(tm any, api schema.Api) (any, error) + Output string +} + +type Module struct { + TypeMap any // Target-specific type map; passed through to ViewModelFactory. + Partials []string // Shared partial template contents (define blocks) available to all productions. + Produces []Production + StaticFiles []StaticFile // Hand-written files to copy verbatim into the output directory. +} + +func (m Module) Generate(api schema.Api, outPath string) error { + if err := os.MkdirAll(outPath, 0755); err != nil { + return fmt.Errorf("creating output directory: %w", err) + } + + for _, p := range m.Produces { + if err := m.generateOne(p, api, outPath); err != nil { + return fmt.Errorf("generating %s: %w", p.Output, err) + } + } + + for _, sf := range m.StaticFiles { + if err := copyStaticFile(sf, outPath); err != nil { + return fmt.Errorf("copying static file %s: %w", sf.Output, err) + } + } + + return nil +} + +func copyStaticFile(sf StaticFile, outPath string) error { + data, err := sf.FS.ReadFile(sf.Source) + if err != nil { + return fmt.Errorf("reading %s: %w", sf.Source, err) + } + return os.WriteFile(filepath.Join(outPath, sf.Output), data, 0644) +} + +func (m Module) generateOne(p Production, api schema.Api, outPath string) error { + tmpl := template.New("codegen").Funcs(p.FuncMap) + + // Parse shared partial templates first so their define blocks are available. + for i, partial := range m.Partials { + if _, err := tmpl.Parse(partial); err != nil { + return fmt.Errorf("parsing partial %d for %s: %w", i, p.Output, err) + } + } + + if _, err := tmpl.Parse(p.TemplateContent); err != nil { + return fmt.Errorf("parsing template for %s: %w", p.Output, err) + } + + vm, err := p.ViewModelFactory(m.TypeMap, api) + if err != nil { + return fmt.Errorf("creating view model: %w", err) + } + + outFilePath := filepath.Join(outPath, p.Output) + outFile, err := os.Create(outFilePath) + if err != nil { + return fmt.Errorf("creating file %s: %w", outFilePath, err) + } + defer outFile.Close() + + if err := tmpl.Execute(outFile, vm); err != nil { + return fmt.Errorf("executing template for %s: %w", p.Output, err) + } + + return nil +} diff --git a/cmd/codegen-modular/dotnet/resolve.go b/cmd/codegen-modular/dotnet/resolve.go new file mode 100644 index 000000000..585af4768 --- /dev/null +++ b/cmd/codegen-modular/dotnet/resolve.go @@ -0,0 +1,287 @@ +package dotnet + +import ( + "fmt" + "strings" + + "github.com/golang-cz/textcase" + "heroiclabs.com/modular-codegen/schema" +) + +// MakeViewModelFactory returns a Production-compatible factory for C# code generation. +func MakeViewModelFactory(typePrefix, namespace string) func(any, schema.Api) (any, error) { + return func(tmRaw any, api schema.Api) (any, error) { + tm := tmRaw.(TypeMap) + return ViewModel{ + namespace: namespace, + typePrefix: typePrefix, + messages: resolveMessages(api, tm, typePrefix), + enums: resolveEnums(api), + methods: resolveMethods(api, tm, typePrefix), + rtOperations: resolveRtOperations(api, tm, typePrefix), + }, nil + } +} + +// resolveField maps a proto field to its C# representation. +func resolveField(name, protoType string, repeated bool, comment string, tm TypeMap, api schema.Api, typePrefix string) Field { + // Strip package qualifier (e.g., "api.Notification" → "Notification"). + if idx := strings.LastIndex(protoType, "."); idx >= 0 { + stripped := protoType[idx+1:] + if _, ok := api.EnumsByName[stripped]; ok { + protoType = stripped + } else if _, ok := api.MessagesByName[stripped]; ok { + protoType = stripped + } + } + + f := Field{ + Name: textcase.PascalCase(name), + ParamName: toCamelCase(name), + JsonName: name, + Comment: comment, + IsRepeated: repeated, + } + + if entry, ok := tm.Resolve(protoType); ok { + f.CSharpType = entry.CSharpType + if repeated { + f.CSharpType = entry.RepeatedType + } + f.IsOptional = !entry.IsValueType || repeated + return f + } + + if _, ok := api.EnumsByName[protoType]; ok { + f.IsEnum = true + f.CSharpType = typePrefix + protoType + if repeated { + f.CSharpType = fmt.Sprintf("List<%s%s>", typePrefix, protoType) + } + return f + } + + if _, ok := api.MessagesByName[protoType]; ok { + f.IsMessage = true + f.CSharpType = typePrefix + protoType + if repeated { + f.CSharpType = fmt.Sprintf("List<%s%s>", typePrefix, protoType) + } + f.IsOptional = true + return f + } + + // Unknown type — default to string. + f.CSharpType = "string" + f.IsOptional = true + return f +} + +func resolveMapField(name, protoType, comment string, tm TypeMap, typePrefix string) Field { + f := Field{ + Name: textcase.PascalCase(name), + ParamName: toCamelCase(name), + JsonName: name, + Comment: comment, + IsMap: true, + IsOptional: true, + } + if entry, ok := tm.Resolve(protoType); ok { + f.CSharpType = entry.MapType + } else { + f.CSharpType = fmt.Sprintf("Dictionary", typePrefix, protoType) + } + return f +} + +func resolveMessages(api schema.Api, tm TypeMap, typePrefix string) []Message { + var result []Message + for _, msg := range api.Messages { + m := Message{ + Name: msg.Name, + Comment: msg.Comment, + } + for _, f := range msg.Fields { + m.Fields = append(m.Fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, typePrefix)) + } + for _, f := range msg.MapFields { + m.MapFields = append(m.MapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, typePrefix)) + } + result = append(result, m) + } + return result +} + +func resolveEnums(api schema.Api) []Enum { + var result []Enum + for _, enum := range api.Enums { + e := Enum{ + Name: enum.Name, + Comment: enum.Comment, + } + for i, f := range enum.Fields { + e.Values = append(e.Values, EnumValue{ + Name: f.Name, + Value: f.Integer, + IsLast: i == len(enum.Fields)-1, + }) + } + result = append(result, e) + } + return result +} + +func resolveMethods(api schema.Api, tm TypeMap, typePrefix string) []Method { + var result []Method + for _, rpc := range api.Rpcs { + result = append(result, resolveMethod(rpc, tm, api, typePrefix)) + } + return result +} + +func resolveMethod(rpc *schema.VisitedRpc, tm TypeMap, api schema.Api, typePrefix string) Method { + isAuth := strings.Contains(rpc.Name, "Authenticate") + isSessionRefresh := rpc.Name == "SessionRefresh" + + m := Method{ + Name: textcase.PascalCase(rpc.Name), + Comment: rpc.Comment, + HttpMethod: rpc.Method, + Endpoint: rpc.Endpoint, + NeedsAuth: !isAuth, + AuthType: "Bearer", + BodyMode: "none", + } + + if isAuth || isSessionRefresh { + m.AuthType = "Basic" + } + + // Return type. + if rpc.ReturnType != nil { + m.HasReturn = true + m.ReturnType = rpc.ReturnType.Name + } + + if rpc.RequestType == nil { + return m + } + + // Build path param set. + pathSet := make(map[string]bool) + for _, p := range rpc.PathParams { + if p != "" { + pathSet[p] = true + } + } + + // Resolve and classify fields. + isGet := rpc.Method == "GET" + bodyField := rpc.BodyField + + for _, f := range rpc.RequestType.Fields { + rf := resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, typePrefix) + + switch { + case pathSet[f.Name]: + m.PathParams = append(m.PathParams, rf) + case isQueryParam(f.Name, bodyField, isGet): + rf.IsOptional = true + m.QueryParams = append(m.QueryParams, rf) + m.AllParams = append(m.AllParams, rf) + default: + m.BodyParams = append(m.BodyParams, rf) + m.AllParams = append(m.AllParams, rf) + } + } + + for _, f := range rpc.RequestType.MapFields { + rf := resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, typePrefix) + // Map fields go to body in wildcard mode, query otherwise. + if bodyField == "*" && !isGet { + m.BodyParams = append(m.BodyParams, rf) + } else { + m.QueryParams = append(m.QueryParams, rf) + } + m.AllParams = append(m.AllParams, rf) + } + + // Determine body mode. + switch { + case bodyField == "*" && !isGet: + m.BodyMode = "wildcard" + case bodyField != "" && bodyField != "*": + for _, f := range m.BodyParams { + if f.JsonName == bodyField { + m.BodyFieldName = f.Name + if f.IsMessage { + m.BodyMode = "object" + } else { + m.BodyMode = "scalar" + } + break + } + } + } + + return m +} + +func resolveRtOperations(api schema.Api, tm TypeMap, typePrefix string) []RtOperation { + envelope, ok := api.MessagesByName["Envelope"] + if !ok { + return nil + } + + var result []RtOperation + for _, oneof := range envelope.OneofFields { + typeName := oneof.Name[strings.LastIndex(oneof.Name, ".")+1:] + msg, ok := api.MessagesByName[typeName] + if !ok { + continue + } + + op := RtOperation{ + CaseName: oneof.Name, + MessageName: msg.Name, + Comment: msg.Comment, + } + + for _, f := range msg.Fields { + op.Fields = append(op.Fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, typePrefix)) + } + for _, f := range msg.MapFields { + op.MapFields = append(op.MapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, typePrefix)) + } + for _, f := range msg.OneofFields { + op.OneofFields = append(op.OneofFields, Field{ + Name: toCamelCase(f.Name), + JsonName: f.Name, + CSharpType: "string", + IsOptional: true, + }) + } + + result = append(result, op) + } + return result +} + +func isQueryParam(jsonName, bodyField string, isGet bool) bool { + if isGet || bodyField == "" { + return true + } + if bodyField == "*" { + return false + } + return jsonName != bodyField +} + +// toCamelCase converts a snake_case name to camelCase for C# method parameters. +func toCamelCase(s string) string { + pascal := textcase.PascalCase(s) + if len(pascal) == 0 { + return pascal + } + return strings.ToLower(pascal[:1]) + pascal[1:] +} diff --git a/cmd/codegen-modular/dotnet/templates.go b/cmd/codegen-modular/dotnet/templates.go new file mode 100644 index 000000000..3e2838460 --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates.go @@ -0,0 +1,6 @@ +package dotnet + +import "embed" + +//go:embed templates/*.tmpl +var Templates embed.FS diff --git a/cmd/codegen-modular/dotnet/templates/api-client.cs.tmpl b/cmd/codegen-modular/dotnet/templates/api-client.cs.tmpl new file mode 100644 index 000000000..beaf2e4ca --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates/api-client.cs.tmpl @@ -0,0 +1,120 @@ +// Copyright {{currentYear}} The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// Generated by modular-codegen. DO NOT EDIT. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace {{ $.Namespace }} +{ + /// + /// Generated API client for {{ $.Namespace }}. + /// HTTP transport and auth are provided by the hand-written partial class. + /// + public partial class {{ $.TypePrefix }}Client + { + {{- range $m := $.Methods }} + + /// + /// {{ $m.Comment }} + /// + public async Task{{ if $m.HasReturn }}<{{ $.TypePrefix }}{{ $m.ReturnType }}>{{ end }} {{ $m.Name }}Async( + {{- if $m.NeedsAuth }} + string {{ if eq $m.AuthType "Basic" }}basicAuth{{ else }}bearerToken{{ end }}, + {{- end }} + {{- range $m.PathParams }} + string {{ .ParamName }}, + {{- end }} + {{- range $m.AllParams }} + {{- if .IsOptional }} + {{ .CSharpType }}? {{ .ParamName }} = null, + {{- else }} + {{ .CSharpType }} {{ .ParamName }}, + {{- end }} + {{- end }} + CancellationToken cancellationToken = default) + { + var path = "{{ $m.Endpoint }}"; + {{- range $m.PathParams }} + path = path.Replace("{{ printf "{%s}" .JsonName }}", Uri.EscapeDataString({{ .ParamName }})); + {{- end }} + {{- if $m.QueryParams }} + + var queryParams = new List>(); + {{- range $m.QueryParams }} + {{- if .IsRepeated }} + if ({{ .ParamName }} != null) + { + foreach (var v in {{ .ParamName }}) + { + queryParams.Add(new KeyValuePair("{{ .JsonName }}", v.ToString())); + } + } + {{- else if .IsOptional }} + if ({{ .ParamName }} != null) + { + queryParams.Add(new KeyValuePair("{{ .JsonName }}", {{ .ParamName }}{{ if .IsEnum }}.ToString(){{ else if not .IsMessage }}.ToString(){{ end }})); + } + {{- else }} + queryParams.Add(new KeyValuePair("{{ .JsonName }}", {{ .ParamName }}.ToString())); + {{- end }} + {{- end }} + {{- end }} + + {{- if eq $m.BodyMode "wildcard" }} + + var body = new Dictionary(); + {{- range $m.BodyParams }} + if ({{ .ParamName }} != null) body["{{ .JsonName }}"] = {{ .ParamName }}; + {{- end }} + {{- end }} + + {{- if $m.NeedsAuth }} + var auth = {{ if eq $m.AuthType "Basic" }}basicAuth{{ else }}bearerToken{{ end }}; + {{- else }} + string? auth = null; + {{- end }} + + {{- if $m.HasReturn }} + return await SendAsync<{{ $.TypePrefix }}{{ $m.ReturnType }}>( + {{- else }} + await SendAsync( + {{- end }} + "{{ $m.HttpMethod }}", + path, + auth, + {{- if $m.QueryParams }} + queryParams, + {{- else }} + null, + {{- end }} + {{- if eq $m.BodyMode "wildcard" }} + body, + {{- else if eq $m.BodyMode "object" }} + {{ $m.BodyFieldName | toLower }}, + {{- else }} + null, + {{- end }} + cancellationToken); + } + {{- end }} + } +} diff --git a/cmd/codegen-modular/dotnet/templates/enums.cs.tmpl b/cmd/codegen-modular/dotnet/templates/enums.cs.tmpl new file mode 100644 index 000000000..e2401ea9a --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates/enums.cs.tmpl @@ -0,0 +1,33 @@ +// Copyright {{currentYear}} The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// Generated by modular-codegen. DO NOT EDIT. +// + +namespace {{ $.Namespace }} +{ +{{- range $enum := $.Enums }} + + /// + /// {{ $enum.Comment }} + /// + public enum {{ $.TypePrefix }}{{ $enum.Name }} + { + {{- range $enum.Values }} + {{ .Name }} = {{ .Value }}{{ if not .IsLast }},{{ end }} + {{- end }} + } +{{- end }} +} diff --git a/cmd/codegen-modular/dotnet/templates/rt-client.cs.tmpl b/cmd/codegen-modular/dotnet/templates/rt-client.cs.tmpl new file mode 100644 index 000000000..92c289f3e --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates/rt-client.cs.tmpl @@ -0,0 +1,73 @@ +// Copyright {{currentYear}} The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// Generated by modular-codegen. DO NOT EDIT. +// + +#nullable enable + +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace {{ $.Namespace }}.Realtime +{ + /// + /// Generated realtime client for {{ $.Namespace }}. + /// WebSocket transport is provided by the hand-written partial class. + /// + public partial class {{ $.TypePrefix }}RealtimeClient + { + {{- range $op := $.RtOperations }} + + /// + /// {{ $op.Comment }} + /// + public async Task {{ $op.MessageName }}Async( + {{- range $op.OneofFields }} + string? {{ .Name }} = null, + {{- end }} + {{- range $op.Fields }} + {{- if .IsOptional }} + {{ .CSharpType }}? {{ .ParamName }} = null, + {{- else }} + {{ .CSharpType }} {{ .ParamName }}, + {{- end }} + {{- end }} + {{- range $op.MapFields }} + {{ .CSharpType }}? {{ .ParamName }} = null, + {{- end }} + CancellationToken cancellationToken = default) + { + var payload = new Dictionary(); + {{- range $op.OneofFields }} + if ({{ .Name }} != null) payload["{{ .JsonName }}"] = {{ .Name }}; + {{- end }} + {{- range $op.Fields }} + {{- if .IsOptional }} + if ({{ .ParamName }} != null) payload["{{ .JsonName }}"] = {{ .ParamName }}; + {{- else }} + payload["{{ .JsonName }}"] = {{ .ParamName }}; + {{- end }} + {{- end }} + {{- range $op.MapFields }} + if ({{ .ParamName }} != null) payload["{{ .JsonName }}"] = {{ .ParamName }}; + {{- end }} + return await SendAsync("{{ $op.CaseName }}", payload, cancellationToken); + } + {{- end }} + } +} diff --git a/cmd/codegen-modular/dotnet/templates/rt-types.cs.tmpl b/cmd/codegen-modular/dotnet/templates/rt-types.cs.tmpl new file mode 100644 index 000000000..a5950098c --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates/rt-types.cs.tmpl @@ -0,0 +1,49 @@ +// Copyright {{currentYear}} The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// Generated by modular-codegen. DO NOT EDIT. +// + +#nullable enable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace {{ $.Namespace }}.Realtime +{ +{{- range $op := $.RtOperations }} + + /// + /// {{ $op.Comment }} + /// + public class {{ $.TypePrefix }}Rt{{ $op.MessageName }} + { + {{- range $op.Fields }} + /// {{ .Comment }} + [JsonPropertyName("{{ .JsonName }}")] + public {{ .CSharpType }}? {{ .Name }} { get; set; } + {{- end }} + {{- range $op.MapFields }} + /// {{ .Comment }} + [JsonPropertyName("{{ .JsonName }}")] + public {{ .CSharpType }}? {{ .Name }} { get; set; } + {{- end }} + {{- range $op.OneofFields }} + [JsonPropertyName("{{ .JsonName }}")] + public string? {{ .Name }} { get; set; } + {{- end }} + } +{{- end }} +} diff --git a/cmd/codegen-modular/dotnet/templates/types.cs.tmpl b/cmd/codegen-modular/dotnet/templates/types.cs.tmpl new file mode 100644 index 000000000..51b3047ce --- /dev/null +++ b/cmd/codegen-modular/dotnet/templates/types.cs.tmpl @@ -0,0 +1,45 @@ +// Copyright {{currentYear}} The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// Generated by modular-codegen. DO NOT EDIT. +// + +#nullable enable + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace {{ $.Namespace }} +{ +{{- range $msg := $.Messages }} + + /// + /// {{ $msg.Comment }} + /// + public class {{ $.TypePrefix }}{{ $msg.Name }} + { + {{- range $msg.Fields }} + /// {{ .Comment }} + [JsonPropertyName("{{ .JsonName }}")] + public {{ .CSharpType }}{{ if not .IsRepeated }}?{{ end }} {{ .Name }} { get; set; } + {{- end }} + {{- range $msg.MapFields }} + /// {{ .Comment }} + [JsonPropertyName("{{ .JsonName }}")] + public {{ .CSharpType }}? {{ .Name }} { get; set; } + {{- end }} + } +{{- end }} +} diff --git a/cmd/codegen-modular/dotnet/typemap.go b/cmd/codegen-modular/dotnet/typemap.go new file mode 100644 index 000000000..2c7e4e19a --- /dev/null +++ b/cmd/codegen-modular/dotnet/typemap.go @@ -0,0 +1,135 @@ +package dotnet + +// TypeEntry maps a proto primitive to its C# representation. +type TypeEntry struct { + CSharpType string // Scalar type: "string", "int", "bool", "long" + NullableType string // Wrapper/nullable: "string", "int?", "bool?" + RepeatedType string // List form: "List", "List" + MapType string // Map form: "Dictionary" + DefaultExpr string // Default for optional params: "null", "default" + IsValueType bool // True for int, bool, long, etc. (affects null checks) +} + +// TypeMap resolves proto type names to C# type traits. +type TypeMap interface { + Resolve(protoType string) (*TypeEntry, bool) +} + +type DotnetTypeMap struct { + entries map[string]*TypeEntry +} + +func NewDotnetTypeMap() *DotnetTypeMap { + stringEntry := &TypeEntry{ + CSharpType: "string", + NullableType: "string", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "null", + } + + boolEntry := &TypeEntry{ + CSharpType: "bool", + NullableType: "bool?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + intEntry := &TypeEntry{ + CSharpType: "int", + NullableType: "int?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + uintEntry := &TypeEntry{ + CSharpType: "uint", + NullableType: "uint?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + longEntry := &TypeEntry{ + CSharpType: "long", + NullableType: "long?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + ulongEntry := &TypeEntry{ + CSharpType: "ulong", + NullableType: "ulong?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + floatEntry := &TypeEntry{ + CSharpType: "float", + NullableType: "float?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + doubleEntry := &TypeEntry{ + CSharpType: "double", + NullableType: "double?", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "default", + IsValueType: true, + } + + bytesEntry := &TypeEntry{ + CSharpType: "string", + NullableType: "string", + RepeatedType: "List", + MapType: "Dictionary", + DefaultExpr: "null", + } + + return &DotnetTypeMap{ + entries: map[string]*TypeEntry{ + "string": stringEntry, + "google.protobuf.StringValue": stringEntry, + "google.protobuf.Timestamp": stringEntry, + + "bool": boolEntry, + "google.protobuf.BoolValue": {CSharpType: "bool?", NullableType: "bool?", RepeatedType: "List", DefaultExpr: "null"}, + + "int32": intEntry, + "google.protobuf.Int32Value": {CSharpType: "int?", NullableType: "int?", RepeatedType: "List", DefaultExpr: "null"}, + "uint32": uintEntry, + "google.protobuf.UInt32Value": {CSharpType: "uint?", NullableType: "uint?", RepeatedType: "List", DefaultExpr: "null"}, + + "int64": longEntry, + "google.protobuf.Int64Value": {CSharpType: "long?", NullableType: "long?", RepeatedType: "List", DefaultExpr: "null"}, + "uint64": ulongEntry, + "google.protobuf.UInt64Value": {CSharpType: "ulong?", NullableType: "ulong?", RepeatedType: "List", DefaultExpr: "null"}, + + "float": floatEntry, + "google.protobuf.FloatValue": {CSharpType: "float?", NullableType: "float?", RepeatedType: "List", DefaultExpr: "null"}, + "double": doubleEntry, + "google.protobuf.DoubleValue": {CSharpType: "double?", NullableType: "double?", RepeatedType: "List", DefaultExpr: "null"}, + + "bytes": bytesEntry, + "google.protobuf.BytesValue": bytesEntry, + }, + } +} + +func (m *DotnetTypeMap) Resolve(protoType string) (*TypeEntry, bool) { + e, ok := m.entries[protoType] + return e, ok +} diff --git a/cmd/codegen-modular/dotnet/viewmodel.go b/cmd/codegen-modular/dotnet/viewmodel.go new file mode 100644 index 000000000..6fdd826c5 --- /dev/null +++ b/cmd/codegen-modular/dotnet/viewmodel.go @@ -0,0 +1,90 @@ +package dotnet + +// Field is a resolved proto field with C# type metadata. +type Field struct { + Name string // PascalCase property name + ParamName string // camelCase parameter name + JsonName string // Original proto snake_case name + Comment string + CSharpType string // Full C# type (e.g., "string", "int", "List") + IsRepeated bool + IsMap bool + IsMessage bool + IsEnum bool + IsOptional bool // Has a sensible default (reference types, repeated, maps) +} + +// Message is a resolved proto message. +type Message struct { + Name string + Comment string + Fields []Field + MapFields []Field +} + +// EnumValue is a single enum constant. +type EnumValue struct { + Name string + Value int + IsLast bool +} + +// Enum is a resolved proto enum. +type Enum struct { + Name string + Comment string + Values []EnumValue +} + +// Method is a resolved RPC with all routing and parameter metadata. +type Method struct { + Name string // PascalCase (e.g., "AddFriends") + Comment string + HttpMethod string // "GET", "POST", "PUT", "DELETE" + Endpoint string // URL pattern (e.g., "/v2/friend") + + // Authentication + AuthType string // "Bearer", "Basic" + NeedsAuth bool // Whether the method takes an auth token param + + // Parameters (all request fields, fully resolved) + AllParams []Field // All non-path, non-body fields (for method signature) + PathParams []Field // Substituted into the URL + QueryParams []Field // Appended as query string + BodyParams []Field // Serialized into the request body + + // Body + BodyMode string // "none", "scalar", "object", "wildcard" + BodyFieldName string // PascalCase name of the single body field + + // Return type + HasReturn bool + ReturnType string // C# type name without prefix (e.g., "Account") +} + +// RtOperation is a resolved realtime WebSocket operation. +type RtOperation struct { + CaseName string // The oneof case name (WebSocket protocol key) + MessageName string // PascalCase type name + Comment string + Fields []Field + MapFields []Field + OneofFields []Field +} + +// ViewModel is the top-level data object passed to all C# templates. +type ViewModel struct { + namespace string + typePrefix string // "Api" or product-specific + messages []Message + enums []Enum + methods []Method + rtOperations []RtOperation +} + +func (vm ViewModel) Namespace() string { return vm.namespace } +func (vm ViewModel) TypePrefix() string { return vm.typePrefix } +func (vm ViewModel) Messages() []Message { return vm.messages } +func (vm ViewModel) Enums() []Enum { return vm.enums } +func (vm ViewModel) Methods() []Method { return vm.methods } +func (vm ViewModel) RtOperations() []RtOperation { return vm.rtOperations } diff --git a/cmd/codegen-modular/go.mod b/cmd/codegen-modular/go.mod new file mode 100644 index 000000000..c6acde372 --- /dev/null +++ b/cmd/codegen-modular/go.mod @@ -0,0 +1,8 @@ +module heroiclabs.com/modular-codegen + +go 1.22.2 + +require ( + github.com/emicklei/proto v1.14.3 + github.com/golang-cz/textcase v1.2.1 +) diff --git a/cmd/codegen-modular/go.sum b/cmd/codegen-modular/go.sum new file mode 100644 index 000000000..609ea0582 --- /dev/null +++ b/cmd/codegen-modular/go.sum @@ -0,0 +1,4 @@ +github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q= +github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/golang-cz/textcase v1.2.1 h1:0xRtKo+abtJojre5ONjuMzyg9fSfiKBj5bWZ6fpTYxI= +github.com/golang-cz/textcase v1.2.1/go.mod h1:aWsQknYwxtTS2zSCrGGoRIsxmzjsHomRqLeMeVb+SKU= diff --git a/cmd/codegen-modular/go.work b/cmd/codegen-modular/go.work new file mode 100644 index 000000000..0f0141055 --- /dev/null +++ b/cmd/codegen-modular/go.work @@ -0,0 +1,6 @@ +go 1.22.2 + +use ( + ./yacg + ./unreal-sdk +) diff --git a/cmd/codegen-modular/protos/nakama-api.proto b/cmd/codegen-modular/protos/nakama-api.proto new file mode 100644 index 000000000..d130c2101 --- /dev/null +++ b/cmd/codegen-modular/protos/nakama-api.proto @@ -0,0 +1,760 @@ +// Copyright 2018 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The Nakama server RPC protocol for games and apps. + */ +syntax = "proto3"; + +package nakama.api; + +import "api/api.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/heroiclabs/nakama/v3/apigrpc"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaApiGrpc"; +option java_package = "com.heroiclabs.nakama.api"; + +option csharp_namespace = "Nakama.Protobuf"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "Nakama API v2"; + version: "2.0"; + contact: { + name: "The Nakama Authors & Contributors"; + url: "https://github.com/heroiclabs/nakama"; + email: "hello@heroiclabs.com"; + }; + }; + host: "127.0.0.1:7350"; + external_docs: { + url: "https://heroiclabs.com/docs"; + description: "Nakama server documentation"; + } + schemes: HTTP; + consumes: "application/json"; + produces: "application/json"; + security_definitions: { + security: { + key: "BasicAuth"; + value: { + type: TYPE_BASIC; + } + } + security: { + key: "BearerJwt" + value: { + type: TYPE_API_KEY + in: IN_HEADER + name: "Authorization" + } + } + security: { + key: "HttpKeyAuth"; + value: { + type: TYPE_API_KEY; + in: IN_HEADER; + name: "http_key"; + } + } + } + // Default security definition. + security: { + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } +}; + +/** + * The Nakama RPC protocol service built with GRPC. + */ +service Nakama { + // Add friends by ID or username to a user's account. + rpc AddFriends (api.AddFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/friend"; + } + + // Add users to a group. + rpc AddGroupUsers (api.AddGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/add"; + } + + // Refresh a user's session using a refresh token retrieved from a previous authentication request. + rpc SessionRefresh (api.SessionRefreshRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/session/refresh", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + rpc SessionLogout (api.SessionLogoutRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/session/logout", + body: "*" + }; + } + + // Authenticate a user with an Apple ID against the server. + rpc AuthenticateApple (api.AuthenticateAppleRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/apple", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a custom id against the server. + rpc AuthenticateCustom (api.AuthenticateCustomRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/custom", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a device id against the server. + rpc AuthenticateDevice (api.AuthenticateDeviceRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/device", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with an email+password against the server. + rpc AuthenticateEmail (api.AuthenticateEmailRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/email", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a Facebook OAuth token against the server. + rpc AuthenticateFacebook (api.AuthenticateFacebookRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/facebook", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a Facebook Instant Game token against the server. + rpc AuthenticateFacebookInstantGame (api.AuthenticateFacebookInstantGameRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/facebookinstantgame", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Apple's GameCenter against the server. + rpc AuthenticateGameCenter (api.AuthenticateGameCenterRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/gamecenter", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Google against the server. + rpc AuthenticateGoogle (api.AuthenticateGoogleRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/google", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Steam against the server. + rpc AuthenticateSteam (api.AuthenticateSteamRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/steam", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Ban a set of users from a group. + rpc BanGroupUsers (api.BanGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/ban"; + } + + // Block one or more users by ID or username. + rpc BlockFriends (api.BlockFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/block" + }; + } + + // Create a new group with the current user as the owner. + rpc CreateGroup (api.CreateGroupRequest) returns (api.Group) { + option (google.api.http) = { + post: "/v2/group", + body: "*" + }; + } + + // Delete the current user's account. + rpc DeleteAccount (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/account"; + } + + // Delete one or more users by ID or username. + rpc DeleteFriends (api.DeleteFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/friend"; + } + + // Delete a group by ID. + rpc DeleteGroup (api.DeleteGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/group/{group_id}"; + } + + // Delete a leaderboard record. + rpc DeleteLeaderboardRecord (api.DeleteLeaderboardRecordRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/leaderboard/{leaderboard_id}"; + } + + // Delete one or more notifications for the current user. + rpc DeleteNotifications (api.DeleteNotificationsRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/notification"; + } + + // Delete a tournament record. + rpc DeleteTournamentRecord (api.DeleteTournamentRecordRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/tournament/{tournament_id}"; + } + + // Delete one or more objects by ID or username. + rpc DeleteStorageObjects (api.DeleteStorageObjectsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/storage/delete", + body: "*" + }; + } + + // Submit an event for processing in the server's registered runtime custom events handler. + rpc Event (api.Event) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/event", + body: "*" + }; + } + + // Fetch the current user's account. + rpc GetAccount (google.protobuf.Empty) returns (api.Account) { + option (google.api.http).get = "/v2/account"; + } + + // Fetch zero or more users by ID and/or username. + rpc GetUsers (api.GetUsersRequest) returns (api.Users) { + option (google.api.http).get = "/v2/user"; + } + + // Get subscription by product id. + rpc GetSubscription (api.GetSubscriptionRequest) returns (api.ValidatedSubscription) { + option (google.api.http).get = "/v2/iap/subscription/{product_id}"; + } + + // Get matchmaker stats. + rpc GetMatchmakerStats (google.protobuf.Empty) returns (api.MatchmakerStats) { + option (google.api.http).get = "/v2/matchmaker/stats"; + } + + // A healthcheck which load balancers can use to check the service. + rpc Healthcheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/healthcheck"; + } + + // Import Facebook friends and add them to a user's account. + rpc ImportFacebookFriends (api.ImportFacebookFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/facebook", + body: "account" + }; + } + + // Import Steam friends and add them to a user's account. + rpc ImportSteamFriends (api.ImportSteamFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/steam", + body: "account" + }; + } + + // Immediately join an open group, or request to join a closed one. + rpc JoinGroup (api.JoinGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/join"; + } + + // Attempt to join an open and running tournament. + rpc JoinTournament (api.JoinTournamentRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/tournament/{tournament_id}/join"; + } + + // Kick a set of users from a group. + rpc KickGroupUsers (api.KickGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/kick"; + } + + // Leave a group the user is a member of. + rpc LeaveGroup (api.LeaveGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/leave"; + } + + // Add an Apple ID to the social profiles on the current user's account. + rpc LinkApple (api.AccountApple) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/apple", + body: "*" + }; + } + + // Add a custom ID to the social profiles on the current user's account. + rpc LinkCustom (api.AccountCustom) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/custom", + body: "*" + }; + } + + // Add a device ID to the social profiles on the current user's account. + rpc LinkDevice (api.AccountDevice) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/device", + body: "*" + }; + } + + // Add an email+password to the social profiles on the current user's account. + rpc LinkEmail (api.AccountEmail) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/email", + body: "*" + }; + } + + // Add Facebook to the social profiles on the current user's account. + rpc LinkFacebook (api.LinkFacebookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/facebook", + body: "account" + }; + } + + // Add Facebook Instant Game to the social profiles on the current user's account. + rpc LinkFacebookInstantGame (api.AccountFacebookInstantGame) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/facebookinstantgame", + body: "*" + }; + } + + // Add Apple's GameCenter to the social profiles on the current user's account. + rpc LinkGameCenter (api.AccountGameCenter) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/gamecenter", + body: "*" + }; + } + + // Add Google to the social profiles on the current user's account. + rpc LinkGoogle (api.AccountGoogle) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/google", + body: "*" + }; + } + + // Add Steam to the social profiles on the current user's account. + rpc LinkSteam (api.LinkSteamRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/steam", + body: "*" + }; + } + + // List a channel's message history. + rpc ListChannelMessages (api.ListChannelMessagesRequest) returns (api.ChannelMessageList) { + option (google.api.http).get = "/v2/channel/{channel_id}"; + } + + // List all friends for the current user. + rpc ListFriends (api.ListFriendsRequest) returns (api.FriendList) { + option (google.api.http).get = "/v2/friend"; + } + + // List friends of friends for the current user. + rpc ListFriendsOfFriends(api.ListFriendsOfFriendsRequest) returns (api.FriendsOfFriendsList) { + option (google.api.http).get = "/v2/friend/friends"; + } + + // List groups based on given filters. + rpc ListGroups (api.ListGroupsRequest) returns (api.GroupList) { + option (google.api.http).get = "/v2/group"; + } + + // List all users that are part of a group. + rpc ListGroupUsers (api.ListGroupUsersRequest) returns (api.GroupUserList) { + option (google.api.http).get = "/v2/group/{group_id}/user"; + } + + // List leaderboard records. + rpc ListLeaderboardRecords (api.ListLeaderboardRecordsRequest) returns (api.LeaderboardRecordList) { + option (google.api.http).get = "/v2/leaderboard/{leaderboard_id}"; + } + + // List leaderboard records around the target ownerId. + rpc ListLeaderboardRecordsAroundOwner (api.ListLeaderboardRecordsAroundOwnerRequest) returns (api.LeaderboardRecordList) { + option (google.api.http).get = "/v2/leaderboard/{leaderboard_id}/owner/{owner_id}"; + } + + // List running matches and optionally filter by matching criteria. + rpc ListMatches (api.ListMatchesRequest) returns (api.MatchList) { + option (google.api.http).get = "/v2/match"; + } + + // List parties and optionally filter by matching criteria. + rpc ListParties (api.ListPartiesRequest) returns (api.PartyList) { + option (google.api.http).get = "/v2/party"; + } + + // Fetch list of notifications. + rpc ListNotifications (api.ListNotificationsRequest) returns (api.NotificationList) { + option (google.api.http).get = "/v2/notification"; + } + + // List publicly readable storage objects in a given collection. + rpc ListStorageObjects (api.ListStorageObjectsRequest) returns (api.StorageObjectList) { + option (google.api.http) = { + get: "/v2/storage/{collection}", + additional_bindings { + get: "/v2/storage/{collection}/{user_id}" + } + }; + } + + // List user's subscriptions. + rpc ListSubscriptions (api.ListSubscriptionsRequest) returns (api.SubscriptionList) { + option (google.api.http) = { + post: "/v2/iap/subscription", + body: "*" + }; + } + + // List current or upcoming tournaments. + rpc ListTournaments (api.ListTournamentsRequest) returns (api.TournamentList) { + option (google.api.http).get = "/v2/tournament"; + } + + // List tournament records. + rpc ListTournamentRecords (api.ListTournamentRecordsRequest) returns (api.TournamentRecordList) { + option (google.api.http).get = "/v2/tournament/{tournament_id}"; + } + + // List tournament records for a given owner. + rpc ListTournamentRecordsAroundOwner (api.ListTournamentRecordsAroundOwnerRequest) returns (api.TournamentRecordList) { + option (google.api.http).get = "/v2/tournament/{tournament_id}/owner/{owner_id}"; + } + + // List groups the current user belongs to. + rpc ListUserGroups (api.ListUserGroupsRequest) returns (api.UserGroupList) { + option (google.api.http).get = "/v2/user/{user_id}/group"; + } + + // Promote a set of users in a group to the next role up. + rpc PromoteGroupUsers (api.PromoteGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/promote"; + } + + // Demote a set of users in a group to the next role down. + rpc DemoteGroupUsers (api.DemoteGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/demote"; + } + + // Get storage objects. + rpc ReadStorageObjects (api.ReadStorageObjectsRequest) returns (api.StorageObjects) { + option (google.api.http) = { + post: "/v2/storage", + body: "*" + }; + } + + // Execute a Lua function on the server. + rpc RpcFunc (api.Rpc) returns (api.Rpc) { + option (google.api.http) = { + post: "/v2/rpc/{id}", + body: "payload", + additional_bindings { + get: "/v2/rpc/{id}" + } + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "HttpKeyAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // Remove the Apple ID from the social profiles on the current user's account. + rpc UnlinkApple (api.AccountApple) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/apple", + body: "*" + }; + } + + // Remove the custom ID from the social profiles on the current user's account. + rpc UnlinkCustom (api.AccountCustom) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/custom", + body: "*" + }; + } + + // Remove the device ID from the social profiles on the current user's account. + rpc UnlinkDevice (api.AccountDevice) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/device", + body: "*" + }; + } + + // Remove the email+password from the social profiles on the current user's account. + rpc UnlinkEmail (api.AccountEmail) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/email", + body: "*" + }; + } + + // Remove Facebook from the social profiles on the current user's account. + rpc UnlinkFacebook (api.AccountFacebook) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/facebook", + body: "*" + }; + } + + // Remove Facebook Instant Game profile from the social profiles on the current user's account. + rpc UnlinkFacebookInstantGame (api.AccountFacebookInstantGame) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/facebookinstantgame", + body: "*" + }; + } + + // Remove Apple's GameCenter from the social profiles on the current user's account. + rpc UnlinkGameCenter (api.AccountGameCenter) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/gamecenter", + body: "*" + }; + } + + // Remove Google from the social profiles on the current user's account. + rpc UnlinkGoogle (api.AccountGoogle) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/google", + body: "*" + }; + } + + // Remove Steam from the social profiles on the current user's account. + rpc UnlinkSteam (api.AccountSteam) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/steam", + body: "*" + }; + } + + // Update fields in the current user's account. + rpc UpdateAccount (api.UpdateAccountRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/account", + body: "*" + }; + } + + // Update fields in a given group. + rpc UpdateGroup (api.UpdateGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/group/{group_id}", + body: "*" + }; + } + + // Validate Apple IAP Receipt + rpc ValidatePurchaseApple (api.ValidatePurchaseAppleRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/apple", + body: "*" + }; + } + + // Validate Apple Subscription Receipt + rpc ValidateSubscriptionApple (api.ValidateSubscriptionAppleRequest) returns (api.ValidateSubscriptionResponse) { + option (google.api.http) = { + post: "/v2/iap/subscription/apple", + body: "*" + }; + } + + // Validate Google IAP Receipt + rpc ValidatePurchaseGoogle (api.ValidatePurchaseGoogleRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/google", + body: "*" + }; + } + + // Validate Google Subscription Receipt + rpc ValidateSubscriptionGoogle (api.ValidateSubscriptionGoogleRequest) returns (api.ValidateSubscriptionResponse) { + option (google.api.http) = { + post: "/v2/iap/subscription/google", + body: "*" + }; + } + + // Validate Huawei IAP Receipt + rpc ValidatePurchaseHuawei (api.ValidatePurchaseHuaweiRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/huawei", + body: "*" + }; + } + + // Validate FB Instant IAP Receipt + rpc ValidatePurchaseFacebookInstant (api.ValidatePurchaseFacebookInstantRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/facebookinstant", + body: "*" + }; + } + + // Write a record to a leaderboard. + rpc WriteLeaderboardRecord (api.WriteLeaderboardRecordRequest) returns (api.LeaderboardRecord) { + option (google.api.http) = { + post: "/v2/leaderboard/{leaderboard_id}", + body: "record" + }; + } + + // Write objects into the storage engine. + rpc WriteStorageObjects (api.WriteStorageObjectsRequest) returns (api.StorageObjectAcks) { + option (google.api.http) = { + put: "/v2/storage", + body: "*" + }; + } + + // Write a record to a tournament. + rpc WriteTournamentRecord (api.WriteTournamentRecordRequest) returns (api.LeaderboardRecord) { + option (google.api.http) = { + put: "/v2/tournament/{tournament_id}", + body: "record", + additional_bindings { + post: "/v2/tournament/{tournament_id}", + body: "record", + } + }; + } +} diff --git a/cmd/codegen-modular/protos/nakama-types.proto b/cmd/codegen-modular/protos/nakama-types.proto new file mode 100644 index 000000000..23467996e --- /dev/null +++ b/cmd/codegen-modular/protos/nakama-types.proto @@ -0,0 +1,1459 @@ +// Copyright 2019 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The Nakama server RPC protocol for games and apps. + */ +syntax = "proto3"; + +package nakama.api; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "github.com/heroiclabs/nakama-common/api"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaApi"; +option java_package = "com.heroiclabs.nakama.api"; + +option csharp_namespace = "Nakama.Protobuf"; + + +// A user in the server. +message User { + // The id of the user's account. + string id = 1; + // The username of the user's account. + string username = 2; + // The display name of the user. + string display_name = 3; + // A URL for an avatar image. + string avatar_url = 4; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 5; + // The location set by the user. + string location = 6; + // The timezone set by the user. + string timezone = 7; + // Additional information stored as a JSON object. + string metadata = 8; + // The Facebook id in the user's account. + string facebook_id = 9; + // The Google id in the user's account. + string google_id = 10; + // The Apple Game Center in of the user's account. + string gamecenter_id = 11; + // The Steam id in the user's account. + string steam_id = 12; + // Indicates whether the user is currently online. + bool online = 13; + // Number of related edges to this user. + int32 edge_count = 14; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was created. + google.protobuf.Timestamp create_time = 15; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was last updated. + google.protobuf.Timestamp update_time = 16; + // The Facebook Instant Game ID in the user's account. + string facebook_instant_game_id = 17; + // The Apple Sign In ID in the user's account. + string apple_id = 18; +} + +// Send a device to the server. Used with authenticate/link/unlink and user. +message AccountDevice { + // A device identifier. Should be obtained by a platform-specific device API. + string id = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// A user with additional account details. Always the current user. +message Account { + // The user object. + User user = 1; + // The user's wallet data. + string wallet = 2; + // The email address of the user. + string email = 3; + // The devices which belong to the user's account. + repeated AccountDevice devices = 4; + // The custom id in the user's account. + string custom_id = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's email was verified. + google.protobuf.Timestamp verify_time = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's account was disabled/banned. + google.protobuf.Timestamp disable_time = 7; +} + +// Obtain a new authentication token using a refresh token. +message AccountRefresh { + // Refresh token. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Apple Sign In token to the server. Used with authenticate/link/unlink. +message AccountApple { + // The ID token received from Apple to validate. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a custom ID to the server. Used with authenticate/link/unlink. +message AccountCustom { + // A custom identifier. + string id = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + + +// Send an email with password to the server. Used with authenticate/link/unlink. +message AccountEmail { + // A valid RFC-5322 email address. + string email = 1; + // A password for the user account. + string password = 2; // Ignored with unlink operations. + // Extra information that will be bundled in the session token. + map vars = 3; +} + +// Send a Facebook token to the server. Used with authenticate/link/unlink. +message AccountFacebook { + // The OAuth token received from Facebook to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Facebook Instant Game token to the server. Used with authenticate/link/unlink. +message AccountFacebookInstantGame { + // The OAuth token received from a Facebook Instant Game that may be decoded with the Application Secret (must be available with the nakama configuration) + string signed_player_info = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send Apple's Game Center account credentials to the server. Used with authenticate/link/unlink. +message AccountGameCenter { + // https://developer.apple.com/documentation/gamekit/gklocalplayer/1515407-generateidentityverificationsign + + // Player ID (generated by GameCenter). + string player_id = 1; + // Bundle ID (generated by GameCenter). + string bundle_id = 2; + // Time since UNIX epoch when the signature was created. + int64 timestamp_seconds = 3; + // A random "NSString" used to compute the hash and keep it randomized. + string salt = 4; + // The verification signature data generated. + string signature = 5; + // The URL for the public encryption key. + string public_key_url = 6; + // Extra information that will be bundled in the session token. + map vars = 7; +} + +// Send a Google token to the server. Used with authenticate/link/unlink. +message AccountGoogle { + // The OAuth token received from Google to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Steam token to the server. Used with authenticate/link/unlink. +message AccountSteam { + // The account token received from Steam to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Add one or more friends to the current user. +message AddFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; + // Optional metadata to add to friends. + string metadata = 3; +} + +// Add users to a group. +message AddGroupUsersRequest { + // The group to add users to. + string group_id = 1; + // The users to add. + repeated string user_ids = 2; +} + +// Authenticate against the server with a refresh token. +message SessionRefreshRequest { + // Refresh token. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. +message SessionLogoutRequest { + // Session token to log out. + string token = 1; + // Refresh token to invalidate. + string refresh_token = 2; +} + +// Authenticate against the server with Apple Sign In. +message AuthenticateAppleRequest { + // The Apple account details. + AccountApple account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with a custom ID. +message AuthenticateCustomRequest { + // The custom account details. + AccountCustom account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with a device ID. +message AuthenticateDeviceRequest { + // The device account details. + AccountDevice account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with email+password. +message AuthenticateEmailRequest { + // The email account details. + AccountEmail account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Facebook. +message AuthenticateFacebookRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; + // Import Facebook friends for the user. + google.protobuf.BoolValue sync = 4; +} + +// Authenticate against the server with Facebook Instant Game token. +message AuthenticateFacebookInstantGameRequest { + // The Facebook Instant Game account details. + AccountFacebookInstantGame account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Apple's Game Center. +message AuthenticateGameCenterRequest { + // The Game Center account details. + AccountGameCenter account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Google. +message AuthenticateGoogleRequest { + // The Google account details. + AccountGoogle account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Steam. +message AuthenticateSteamRequest { + // The Steam account details. + AccountSteam account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; + // Import Steam friends for the user. + google.protobuf.BoolValue sync = 4; +} + +// Ban users from a group. +message BanGroupUsersRequest { + // The group to ban users from. + string group_id = 1; + // The users to ban. + repeated string user_ids = 2; +} + +// Block one or more friends for the current user. +message BlockFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; +} + +// A message sent on a channel. +message ChannelMessage { + // The channel this message belongs to. + string channel_id = 1; + // The unique ID of this message. + string message_id = 2; + // The code representing a message type or category. + google.protobuf.Int32Value code = 3; + // Message sender, usually a user ID. + string sender_id = 4; + // The username of the message sender, if any. + string username = 5; + // The content payload. + string content = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + google.protobuf.Timestamp create_time = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + google.protobuf.Timestamp update_time = 8; + // True if the message was persisted to the channel's history, false otherwise. + google.protobuf.BoolValue persistent = 9; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 10; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 11; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 12; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 13; +} + +// A list of channel messages, usually a result of a list operation. +message ChannelMessageList { + // A list of messages. + repeated ChannelMessage messages = 1; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; + // Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. + string cacheable_cursor = 4; +} + +// Create a group with the current user as owner. +message CreateGroupRequest { + // A unique name for the group. + string name = 1; + // A description for the group. + string description = 2; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 3; + // A URL for an avatar image. + string avatar_url = 4; + // Mark a group as open or not where only admins can accept members. + bool open = 5; + // Maximum number of group members. + int32 max_count = 6; +} + +// Delete one or more friends for the current user. +message DeleteFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; +} + +// Delete a group the user has access to. +message DeleteGroupRequest { + // The id of a group. + string group_id = 1; +} + +// Delete a leaderboard record. +message DeleteLeaderboardRecordRequest { + // The leaderboard ID to delete from. + string leaderboard_id = 1; +} + +// Delete one or more notifications for the current user. +message DeleteNotificationsRequest { + // The id of notifications. + repeated string ids = 1; +} + +// Delete a leaderboard record. +message DeleteTournamentRecordRequest { + // The tournament ID to delete from. + string tournament_id = 1; +} + +// Storage objects to delete. +message DeleteStorageObjectId { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The version hash of the object. + string version = 3; +} + +// Batch delete storage objects. +message DeleteStorageObjectsRequest { + // Batch of storage objects. + repeated DeleteStorageObjectId object_ids = 1; +} + +// Represents an event to be passed through the server to registered event handlers. +message Event { + // An event name, type, category, or identifier. + string name = 1; + // Arbitrary event property values. + map properties = 2; + // The time when the event was triggered. + google.protobuf.Timestamp timestamp = 3; + // True if the event came directly from a client call, false otherwise. + bool external = 4; +} + +// A friend of a user. +message Friend { + // The friendship status. + enum State { + // The user is a friend of the current user. + FRIEND = 0; + // The current user has sent an invite to the user. + INVITE_SENT = 1; + // The current user has received an invite from this user. + INVITE_RECEIVED = 2; + // The current user has blocked this user. + BLOCKED = 3; + } + + // The user object. + User user = 1; + // The friend status. + google.protobuf.Int32Value state = 2; // one of "Friend.State". + // Time of the latest relationship update. + google.protobuf.Timestamp update_time = 3; + // Metadata. + string metadata = 4; +} + +// A collection of zero or more friends of the user. +message FriendList { + // The Friend objects. + repeated Friend friends = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// A friend of a friend. +message FriendsOfFriendsList_FriendOfFriend { + // The user who referred its friend. + string referrer = 1; + // User. + User user = 2; +} + +// A List of friends of friends +message FriendsOfFriendsList { + // User friends of friends. + repeated FriendsOfFriendsList_FriendOfFriend friends_of_friends = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// Fetch a batch of zero or more users from the server. +message GetUsersRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; + // The Facebook ID of a user. + repeated string facebook_ids = 3; +} + +// Fetch a subscription by product id. +message GetSubscriptionRequest { + // Product id of the subscription + string product_id = 1; +} + +// A group in the server. +message Group { + // The id of a group. + string id = 1; + // The id of the user who created the group. + string creator_id = 2; + // The unique name of the group. + string name = 3; + // A description for the group. + string description = 4; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 5; + // Additional information stored as a JSON object. + string metadata = 6; + // A URL for an avatar image. + string avatar_url = 7; + // Anyone can join open groups, otherwise only admins can accept members. + google.protobuf.BoolValue open = 8; + // The current count of all members in the group. + int32 edge_count = 9; + // The maximum number of members allowed. + int32 max_count = 10; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was created. + google.protobuf.Timestamp create_time = 11; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was last updated. + google.protobuf.Timestamp update_time = 12; +} + +// One or more groups returned from a listing operation. +message GroupList { + // One or more groups. + repeated Group groups = 1; + // A cursor used to get the next page. + string cursor = 2; +} + +message GroupUserList_GroupUser { + // The group role status. + enum State { + // The user is a superadmin with full control of the group. + SUPERADMIN = 0; + // The user is an admin with additional privileges. + ADMIN = 1; + // The user is a regular member. + MEMBER = 2; + // The user has requested to join the group + JOIN_REQUEST = 3; + } + // User. + User user = 1; + // Their relationship to the group. + google.protobuf.Int32Value state = 2; +} + +// A list of users belonging to a group, along with their role. +message GroupUserList { + // User-role pairs for a group. + repeated GroupUserList_GroupUser group_users = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// Import Facebook friends into the current user's account. +message ImportFacebookFriendsRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Reset the current user's friends list. + google.protobuf.BoolValue reset = 2; +} + +// Import Facebook friends into the current user's account. +message ImportSteamFriendsRequest { + // The Facebook account details. + AccountSteam account = 1; + // Reset the current user's friends list. + google.protobuf.BoolValue reset = 2; +} + +// Immediately join an open group, or request to join a closed one. +message JoinGroupRequest { + // The group ID to join. The group must already exist. + string group_id = 1; +} + +// The request to join a tournament. +message JoinTournamentRequest { + // The ID of the tournament to join. The tournament must already exist. + string tournament_id = 1; +} + +// Kick a set of users from a group. +message KickGroupUsersRequest { + // The group ID to kick from. + string group_id = 1; + // The users to kick. + repeated string user_ids = 2; +} + +// A leaderboard on the server. +message Leaderboard { + // The ID of the leaderboard. + string id = 1; + // ASC(0) or DESC(1) sort mode of scores in the leaderboard. + uint32 sort_order = 2; + // BEST, SET, INCREMENT or DECREMENT operator mode of the leaderboard. + Operator operator = 3; + // The UNIX time when the leaderboard was previously reset. A computed value. + uint32 prev_reset = 4; + // The UNIX time when the leaderboard is next playable. A computed value. + uint32 next_reset = 5; + // Additional information stored as a JSON object. + string metadata = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard was created. + google.protobuf.Timestamp create_time = 7; + // Whether the leaderboard was created authoritatively or not. + bool authoritative = 8; +} + +// A list of leaderboards +message LeaderboardList { + // The list of leaderboards returned. + repeated Leaderboard leaderboards = 1; + // A pagination cursor (optional). + string cursor = 2; +} + +// Represents a complete leaderboard record with all scores and associated metadata. +message LeaderboardRecord { + // The ID of the leaderboard this score belongs to. + string leaderboard_id = 1; + // The ID of the score owner, usually a user or group. + string owner_id = 2; + // The username of the score owner, if the owner is a user. + google.protobuf.StringValue username = 3; + // The score value. + int64 score = 4; + // An optional subscore value. + int64 subscore = 5; + // The number of submissions to this score record. + int32 num_score = 6; + // Metadata. + string metadata = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was created. + google.protobuf.Timestamp create_time = 8; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was updated. + google.protobuf.Timestamp update_time = 9; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record expires. + google.protobuf.Timestamp expiry_time = 10; + // The rank of this record. + int64 rank = 11; + // The maximum number of score updates allowed by the owner. + uint32 max_num_score = 12; +} + +// A set of leaderboard records, may be part of a leaderboard records page or a batch of individual records. +message LeaderboardRecordList { + // A list of leaderboard records. + repeated LeaderboardRecord records = 1; + // A batched set of leaderboard records belonging to specified owners. + repeated LeaderboardRecord owner_records = 2; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 3; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 4; + // The total number of ranks available. + int64 rank_count = 5; +} + +// Leave a group. +message LeaveGroupRequest { + // The group ID to leave. + string group_id = 1; +} + +// Link Facebook to the current user's account. +message LinkFacebookRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Import Facebook friends for the user. + google.protobuf.BoolValue sync = 2; +} + +// Link Steam to the current user's account. +message LinkSteamRequest { + // The Facebook account details. + AccountSteam account = 1; + // Import Steam friends for the user. + google.protobuf.BoolValue sync = 2; +} + +// List a channel's message history. +message ListChannelMessagesRequest { + // The channel ID to list from. + string channel_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // True if listing should be older messages to newer, false if reverse. + google.protobuf.BoolValue forward = 3; + // A pagination cursor, if any. + string cursor = 4; +} + +// List friends for a user. +message ListFriendsRequest { + // Max number of records to return. Between 1 and 1000. + google.protobuf.Int32Value limit = 1; + // The friend state to list. + google.protobuf.Int32Value state = 2; + // An optional next page cursor. + string cursor = 3; +} + +message ListFriendsOfFriendsRequest { + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 1; + // An optional next page cursor. + string cursor = 2; +} + +// List groups based on given filters. +message ListGroupsRequest { + // List groups that contain this value in their names. + string name = 1; + // Optional pagination cursor. + string cursor = 2; + // Max number of groups to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // Language tag filter + string lang_tag = 4; + // Number of group members + google.protobuf.Int32Value members = 5; + // Optional Open/Closed filter. + google.protobuf.BoolValue open = 6; +} + +// List all users that are part of a group. +message ListGroupUsersRequest { + // The group ID to list from. + string group_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // The group user state to list. + google.protobuf.Int32Value state = 3; + // An optional next page cursor. + string cursor = 4; +} + +// List leaerboard records from a given leaderboard around the owner. +message ListLeaderboardRecordsAroundOwnerRequest { + // The ID of the tournament to list for. + string leaderboard_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.UInt32Value limit = 2; + // The owner to retrieve records around. + string owner_id = 3; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 4; + // A next or previous page cursor. + string cursor = 5; +} + +// List leaderboard records from a given leaderboard. +message ListLeaderboardRecordsRequest { + // The ID of the leaderboard to list for. + string leaderboard_id = 1; + // One or more owners to retrieve records for. + repeated string owner_ids = 2; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // A next or previous page cursor. + string cursor = 4; + // Expiry in seconds (since epoch) to begin fetching records from. Optional. 0 means from current time. + google.protobuf.Int64Value expiry = 5; +} + +// List realtime matches. +message ListMatchesRequest { + // Limit the number of returned matches. + google.protobuf.Int32Value limit = 1; + // Authoritative or relayed matches. + google.protobuf.BoolValue authoritative = 2; + // Label filter. + google.protobuf.StringValue label = 3; + // Minimum user count. + google.protobuf.Int32Value min_size = 4; + // Maximum user count. + google.protobuf.Int32Value max_size = 5; + // Arbitrary label query. + google.protobuf.StringValue query = 6; +} + +// Get a list of unexpired notifications. +message ListNotificationsRequest { + // The number of notifications to get. Between 1 and 100. + google.protobuf.Int32Value limit = 1; + // A cursor to page through notifications. May be cached by clients to get from point in time forwards. + string cacheable_cursor = 2; // value from NotificationList.cacheable_cursor. +} + +// List publicly readable storage objects in a given collection. +message ListStorageObjectsRequest { + // ID of the user. + string user_id = 1; + // The collection which stores the object. + string collection = 2; + // The number of storage objects to list. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // The cursor to page through results from. + string cursor = 4; // value from StorageObjectList.cursor. +} + +// List user subscriptions. +message ListSubscriptionsRequest { + // Max number of results per page + google.protobuf.Int32Value limit = 1; + // Cursor to retrieve a page of records from + string cursor = 2; +} + +// List tournament records from a given tournament around the owner. +message ListTournamentRecordsAroundOwnerRequest { + // The ID of the tournament to list for. + string tournament_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.UInt32Value limit = 2; + // The owner to retrieve records around. + string owner_id = 3; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 4; + // A next or previous page cursor. + string cursor = 5; +} + +// List tournament records from a given tournament. +message ListTournamentRecordsRequest { + // The ID of the tournament to list for. + string tournament_id = 1; + // One or more owners to retrieve records for. + repeated string owner_ids = 2; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // A next or previous page cursor. + string cursor = 4; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 5; +} + +// List active/upcoming tournaments based on given filters. +message ListTournamentsRequest { + // The start of the categories to include. Defaults to 0. + google.protobuf.UInt32Value category_start = 1; + // The end of the categories to include. Defaults to 128. + google.protobuf.UInt32Value category_end = 2; + // The start time for tournaments. Defaults to epoch. + google.protobuf.UInt32Value start_time = 3; + // The end time for tournaments. Defaults to +1 year from current Unix time. + google.protobuf.UInt32Value end_time = 4; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 6; + // A next page cursor for listings (optional). + string cursor = 8; +} + +// List the groups a user is part of, and their relationship to each. +message ListUserGroupsRequest { + // ID of the user. + string user_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // The user group state to list. + google.protobuf.Int32Value state = 3; + // An optional next page cursor. + string cursor = 4; +} + +// Represents a realtime match. +message Match { + // The ID of the match, can be used to join. + string match_id = 1; + // True if it's an server-managed authoritative match, false otherwise. + bool authoritative = 2; + // Match label, if any. + google.protobuf.StringValue label = 3; + // Current number of users in the match. + int32 size = 4; + // Tick Rate + int32 tick_rate = 5; + // Handler name + string handler_name = 6; +} + +// A list of realtime matches. +message MatchList { + // A number of matches corresponding to a list operation. + repeated Match matches = 1; +} + +// Matchmaker ticket completion stats +message MatchmakerCompletionStats { + google.protobuf.Timestamp create_time = 1; + google.protobuf.Timestamp complete_time = 2; +} + +// Matchmaker stats +message MatchmakerStats { + int32 ticket_count = 1; + google.protobuf.Timestamp oldest_ticket_create_time = 2; + repeated MatchmakerCompletionStats completions = 3; +} + +// A notification in the server. +message Notification { + // ID of the Notification. + string id = 1; + // Subject of the notification. + string subject = 2; + // Content of the notification in JSON. + string content = 3; + // Category code for this notification. + int32 code = 4; + // ID of the sender, if a user. Otherwise 'null'. + string sender_id = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the notification was created. + google.protobuf.Timestamp create_time = 6; + // True if this notification was persisted to the database. + bool persistent = 7; +} + +// A collection of zero or more notifications. +message NotificationList { + // Collection of notifications. + repeated Notification notifications = 1; + // Use this cursor to paginate notifications. Cache this to catch up to new notifications. + string cacheable_cursor = 2; +} + +// Promote a set of users in a group to the next role up. +message PromoteGroupUsersRequest { + // The group ID to promote in. + string group_id = 1; + // The users to promote. + repeated string user_ids = 2; +} + +// Demote a set of users in a group to the next role down. +message DemoteGroupUsersRequest { + // The group ID to demote in. + string group_id = 1; + // The users to demote. + repeated string user_ids = 2; +} + +// Storage objects to get. +message ReadStorageObjectId { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The user owner of the object. + string user_id = 3; +} + +// Batch get storage objects. +message ReadStorageObjectsRequest { + // Batch of storage objects. + repeated ReadStorageObjectId object_ids = 1; +} + +// Execute an Lua function on the server. +message Rpc { + // The identifier of the function. + string id = 1; + // The payload of the function which must be a JSON object. + string payload = 2; + // The authentication key used when executed as a non-client HTTP request. + string http_key = 3; +} + +// A user's session used to authenticate messages. +message Session { + // True if the corresponding account was just created, false otherwise. + bool created = 1; + // Authentication credentials. + string token = 2; + // Refresh token that can be used for session token renewal. + string refresh_token = 3; +} + +// An object within the storage engine. +message StorageObject { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The user owner of the object. + string user_id = 3; + // The value of the object. + string value = 4; + // The version hash of the object. + string version = 5; + // The read access permissions for the object. + int32 permission_read = 6; + // The write access permissions for the object. + int32 permission_write = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. + google.protobuf.Timestamp create_time = 8; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. + google.protobuf.Timestamp update_time = 9; +} + +// A storage acknowledgement. +message StorageObjectAck { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The version hash of the object. + string version = 3; + // The owner of the object. + string user_id = 4; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. + google.protobuf.Timestamp create_time = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. + google.protobuf.Timestamp update_time = 6; +} + +// Batch of acknowledgements for the storage object write. +message StorageObjectAcks { + // Batch of storage write acknowledgements. + repeated StorageObjectAck acks = 1; +} + +// Batch of storage objects. +message StorageObjects { + // The batch of storage objects. + repeated StorageObject objects = 1; +} + +// List of storage objects. +message StorageObjectList { + // The list of storage objects. + repeated StorageObject objects = 1; + // The cursor for the next page of results, if any. + string cursor = 2; +} + +// A tournament on the server. +message Tournament { + // The ID of the tournament. + string id = 1; + // The title for the tournament. + string title = 2; + // The description of the tournament. May be blank. + string description = 3; + // The category of the tournament. e.g. "vip" could be category 1. + uint32 category = 4; + // ASC (0) or DESC (1) sort mode of scores in the tournament. + uint32 sort_order = 5; + // The current number of players in the tournament. + uint32 size = 6; + // The maximum number of players for the tournament. + uint32 max_size = 7; + // The maximum score updates allowed per player for the current tournament. + uint32 max_num_score = 8; + // True if the tournament is active and can enter. A computed value. + bool can_enter = 9; + // The UNIX time when the tournament stops being active until next reset. A computed value. + uint32 end_active = 10; + // The UNIX time when the tournament is next playable. A computed value. + uint32 next_reset = 11; + // Additional information stored as a JSON object. + string metadata = 12; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament was created. + google.protobuf.Timestamp create_time = 13; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will start. + google.protobuf.Timestamp start_time = 14; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will be stopped. + google.protobuf.Timestamp end_time = 15; + // Duration of the tournament in seconds. + uint32 duration = 16; + // The UNIX time when the tournament start being active. A computed value. + uint32 start_active = 17; + // The UNIX time when the tournament was last reset. A computed value. + uint32 prev_reset = 18; + // Operator. + Operator operator = 19; + // Whether the leaderboard was created authoritatively or not. + bool authoritative = 20; + // Whether the user must join the tournament before being able to submit scores. + bool join_required = 21; +} + +// A list of tournaments. +message TournamentList { + // The list of tournaments returned. + repeated Tournament tournaments = 1; + // A pagination cursor (optional). + string cursor = 2; +} + +// A set of tournament records which may be part of a tournament records page or a batch of individual records. +message TournamentRecordList { + // A list of tournament records. + repeated LeaderboardRecord records = 1; + // A batched set of tournament records belonging to specified owners. + repeated LeaderboardRecord owner_records = 2; + // The cursor to send when retireving the next page (optional). + string next_cursor = 3; + // The cursor to send when retrieving the previous page (optional). + string prev_cursor = 4; + // The total number of ranks available. + int64 rank_count = 5; +} + +// Update a user's account details. +message UpdateAccountRequest { + // The username of the user's account. + google.protobuf.StringValue username = 1; + // The display name of the user. + google.protobuf.StringValue display_name = 2; + // A URL for an avatar image. + google.protobuf.StringValue avatar_url = 3; + // The language expected to be a tag which follows the BCP-47 spec. + google.protobuf.StringValue lang_tag = 4; + // The location set by the user. + google.protobuf.StringValue location = 5; + // The timezone set by the user. + google.protobuf.StringValue timezone = 6; +} + +// Update fields in a given group. +message UpdateGroupRequest { + // The ID of the group to update. + string group_id = 1; + // Name. + google.protobuf.StringValue name = 2; + // Description string. + google.protobuf.StringValue description = 3; + // Lang tag. + google.protobuf.StringValue lang_tag = 4; + // Avatar URL. + google.protobuf.StringValue avatar_url = 5; + // Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin. + google.protobuf.BoolValue open = 6; +} + +// A single group-role pair. +message UserGroupList_UserGroup { + // The group role status. + enum State { + // The user is a superadmin with full control of the group. + SUPERADMIN = 0; + // The user is an admin with additional privileges. + ADMIN = 1; + // The user is a regular member. + MEMBER = 2; + // The user has requested to join the group + JOIN_REQUEST = 3; + } + + // Group. + Group group = 1; + // The user's relationship to the group. + google.protobuf.Int32Value state = 2; +} + +// A list of groups belonging to a user, along with the user's role in each group. +message UserGroupList { + // Group-role pairs for a user. + repeated UserGroupList_UserGroup user_groups = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// A collection of zero or more users. +message Users { + // The User objects. + repeated User users = 1; +} + +// Apple IAP Purchases validation request +message ValidatePurchaseAppleRequest { + // Base64 encoded Apple receipt data payload. + string receipt = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Apple Subscription validation request +message ValidateSubscriptionAppleRequest { + // Base64 encoded Apple receipt data payload. + string receipt = 1; + // Persist the subscription. + google.protobuf.BoolValue persist = 2; +} + +// Google IAP Purchase validation request +message ValidatePurchaseGoogleRequest { + // JSON encoded Google purchase payload. + string purchase = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Google Subscription validation request +message ValidateSubscriptionGoogleRequest { + // JSON encoded Google purchase payload. + string receipt = 1; + // Persist the subscription. + google.protobuf.BoolValue persist = 2; +} + +// Huawei IAP Purchase validation request +message ValidatePurchaseHuaweiRequest { + // JSON encoded Huawei InAppPurchaseData. + string purchase = 1; + // InAppPurchaseData signature. + string signature = 2; + // Persist the purchase + google.protobuf.BoolValue persist = 3; +} + +// Facebook Instant IAP Purchase validation request +message ValidatePurchaseFacebookInstantRequest { + // Base64 encoded Facebook Instant signedRequest receipt data payload. + string signed_request = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Validation Provider, +enum StoreProvider { + // Apple App Store + APPLE_APP_STORE = 0; + // Google Play Store + GOOGLE_PLAY_STORE = 1; + // Huawei App Gallery + HUAWEI_APP_GALLERY = 2; + // Facebook Instant Store + FACEBOOK_INSTANT_STORE = 3; +} + +// Environment where a purchase/subscription took place, +enum StoreEnvironment { + // Unknown environment. + UNKNOWN = 0; + // Sandbox/test environment. + SANDBOX = 1; + // Production environment. + PRODUCTION = 2; +} + +// Validated Purchase stored by Nakama. +message ValidatedPurchase { + // Purchase User ID. + string user_id = 1; + // Purchase Product ID. + string product_id = 2; + // Purchase Transaction ID. + string transaction_id = 3; + // Store identifier + StoreProvider store = 4; + // Timestamp when the purchase was done. + google.protobuf.Timestamp purchase_time = 5; + // Timestamp when the receipt validation was stored in DB. + google.protobuf.Timestamp create_time = 6; + // Timestamp when the receipt validation was updated in DB. + google.protobuf.Timestamp update_time = 7; + // Timestamp when the purchase was refunded. Set to UNIX + google.protobuf.Timestamp refund_time = 8; + // Raw provider validation response. + string provider_response = 9; + // Whether the purchase was done in production or sandbox environment. + StoreEnvironment environment = 10; + // Whether the purchase had already been validated by Nakama before. + bool seen_before = 11; +} + +// Validate IAP response. +message ValidatePurchaseResponse { + // Newly seen validated purchases. + repeated ValidatedPurchase validated_purchases = 1; +} + +message ValidatedSubscription { + // Subscription User ID. + string user_id = 1; + // Purchase Product ID. + string product_id = 2; + // Purchase Original transaction ID (we only keep track of the original subscription, not subsequent renewals). + string original_transaction_id = 3; + // Store identifier + StoreProvider store = 4; + // UNIX Timestamp when the purchase was done. + google.protobuf.Timestamp purchase_time = 5; + // UNIX Timestamp when the receipt validation was stored in DB. + google.protobuf.Timestamp create_time = 6; + // UNIX Timestamp when the receipt validation was updated in DB. + google.protobuf.Timestamp update_time = 7; + // Whether the purchase was done in production or sandbox environment. + StoreEnvironment environment = 8; + // Subscription expiration time. The subscription can still be auto-renewed to extend the expiration time further. + google.protobuf.Timestamp expiry_time = 9; + // Subscription refund time. If this time is set, the subscription was refunded. + google.protobuf.Timestamp refund_time = 10; + // Raw provider validation response body. + string provider_response = 11; + // Raw provider notification body. + string provider_notification = 12; + // Whether the subscription is currently active or not. + bool active = 13; +} + +// Validate Subscription response. +message ValidateSubscriptionResponse { + ValidatedSubscription validated_subscription = 1; +} + +// A list of validated purchases stored by Nakama. +message PurchaseList { + // Stored validated purchases. + repeated ValidatedPurchase validated_purchases = 1; + // The cursor to send when retrieving the next page, if any. + string cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; +} + +// A list of validated subscriptions stored by Nakama. +message SubscriptionList { + // Stored validated subscriptions. + repeated ValidatedSubscription validated_subscriptions = 1; + // The cursor to send when retrieving the next page, if any. + string cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; +} + +// Record values to write. +message WriteLeaderboardRecordRequest_LeaderboardRecordWrite { + // The score value to submit. + int64 score = 1; + // An optional secondary value. + int64 subscore = 2; + // Optional record metadata. + string metadata = 3; + // Operator override. + Operator operator = 4; +} + +// A request to submit a score to a leaderboard. +message WriteLeaderboardRecordRequest { + // The ID of the leaderboard to write to. + string leaderboard_id = 1; + // Record input. + WriteLeaderboardRecordRequest_LeaderboardRecordWrite record = 2; +} + +// The object to store. +message WriteStorageObject { + // The collection to store the object. + string collection = 1; + // The key for the object within the collection. + string key = 2; + // The value of the object. + string value = 3; + // The version hash of the object to check. Possible values are: ["", "*", "#hash#"]. + string version = 4; // if-match and if-none-match + // The read access permissions for the object. + google.protobuf.Int32Value permission_read = 5; + // The write access permissions for the object. + google.protobuf.Int32Value permission_write = 6; +} + +// Write objects to the storage engine. +message WriteStorageObjectsRequest { + // The objects to store on the server. + repeated WriteStorageObject objects = 1; +} + +// Record values to write. +message WriteTournamentRecordRequest_TournamentRecordWrite { + // The score value to submit. + int64 score = 1; + // An optional secondary value. + int64 subscore = 2; + // A JSON object of additional properties (optional). + string metadata = 3; + // Operator override. + Operator operator = 4; +} + +// A request to submit a score to a tournament. +message WriteTournamentRecordRequest { + // The tournament ID to write the record for. + string tournament_id = 1; + // Record input. + WriteTournamentRecordRequest_TournamentRecordWrite record = 2; +} + +// Operator that can be used to override the one set in the leaderboard. +enum Operator { + // Do not override the leaderboard operator. + NO_OVERRIDE = 0; + // Override the leaderboard operator with BEST. + BEST = 1; + // Override the leaderboard operator with SET. + SET = 2; + // Override the leaderboard operator with INCREMENT. + INCREMENT = 3; + // Override the leaderboard operator with DECREMENT. + DECREMENT = 4; +} + +// A request to list parties. +message ListPartiesRequest { + // Limit the number of returned parties. + google.protobuf.Int32Value limit = 1; + // Optionally filter by open/closed parties. + google.protobuf.BoolValue open = 2; + // Arbitrary label query. + google.protobuf.StringValue query = 3; + // Cursor for the next page of results, if any. + google.protobuf.StringValue cursor = 4; +} + +// Incoming information about a party. +message Party { + // Unique party identifier. + string party_id = 1; + // Open flag. + bool open = 2; + // Hidden flag. + bool hidden = 3; + // Maximum number of party members. + int32 max_size = 4; + // The party label, if any. + string label = 5; +} + +// A list of realtime matches. +message PartyList { + // A number of parties corresponding to a list operation. + repeated Party parties = 1; + // A cursor to send when retrieving the next page, if any. + string cursor = 2; +} diff --git a/cmd/codegen-modular/protos/realtime.proto b/cmd/codegen-modular/protos/realtime.proto new file mode 100644 index 000000000..aac5d1b24 --- /dev/null +++ b/cmd/codegen-modular/protos/realtime.proto @@ -0,0 +1,691 @@ +// Copyright 2019 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The realtime protocol for Nakama server. + */ +syntax = "proto3"; + +package nakama.realtime; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "api/api.proto"; + +option go_package = "github.com/heroiclabs/nakama-common/rtapi"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaRealtime"; +option java_package = "com.heroiclabs.nakama.rtapi"; + +option csharp_namespace = "Nakama.Protobuf"; + +// An envelope for a realtime message. +message Envelope { + string cid = 1; + oneof message { + // A response from a channel join operation. + Channel channel = 2; + // Join a realtime chat channel. + ChannelJoin channel_join = 3; + // Leave a realtime chat channel. + ChannelLeave channel_leave = 4; + // An incoming message on a realtime chat channel. + api.ChannelMessage channel_message = 5; + // An acknowledgement received in response to sending a message on a chat channel. + ChannelMessageAck channel_message_ack = 6; + // Send a message to a realtime chat channel. + ChannelMessageSend channel_message_send = 7; + // Update a message previously sent to a realtime chat channel. + ChannelMessageUpdate channel_message_update = 8; + // Remove a message previously sent to a realtime chat channel. + ChannelMessageRemove channel_message_remove = 9; + // Presence update for a particular realtime chat channel. + ChannelPresenceEvent channel_presence_event = 10; + // Describes an error which occurred on the server. + Error error = 11; + // Incoming information about a realtime match. + Match match = 12; + // A client to server request to create a realtime match. + MatchCreate match_create = 13; + // Incoming realtime match data delivered from the server. + MatchData match_data = 14; + // A client to server request to send data to a realtime match. + MatchDataSend match_data_send = 15; + // A client to server request to join a realtime match. + MatchJoin match_join = 16; + // A client to server request to leave a realtime match. + MatchLeave match_leave = 17; + // Presence update for a particular realtime match. + MatchPresenceEvent match_presence_event = 18; + // Submit a new matchmaking process request. + MatchmakerAdd matchmaker_add = 19; + // A successful matchmaking result. + MatchmakerMatched matchmaker_matched = 20; + // Cancel a matchmaking process using a ticket. + MatchmakerRemove matchmaker_remove = 21; + // A response from starting a new matchmaking process. + MatchmakerTicket matchmaker_ticket = 22; + // Notifications send by the server. + Notifications notifications = 23; + // RPC call or response. + api.Rpc rpc = 24; + // An incoming status snapshot for some set of users. + Status status = 25; + // Start following some set of users to receive their status updates. + StatusFollow status_follow = 26; + // An incoming status update. + StatusPresenceEvent status_presence_event = 27; + // Stop following some set of users to no longer receive their status updates. + StatusUnfollow status_unfollow = 28; + // Set the user's own status. + StatusUpdate status_update = 29; + // A data message delivered over a stream. + StreamData stream_data = 30; + // Presence update for a particular stream. + StreamPresenceEvent stream_presence_event = 31; + // Application-level heartbeat and connection check. + Ping ping = 32; + // Application-level heartbeat and connection check response. + Pong pong = 33; + // Incoming information about a party. + Party party = 34; + // Create a party. + PartyCreate party_create = 35; + // Join a party, or request to join if the party is not open. + PartyJoin party_join = 36; + // Leave a party. + PartyLeave party_leave = 37; + // Promote a new party leader. + PartyPromote party_promote = 38; + // Announcement of a new party leader. + PartyLeader party_leader = 39; + // Accept a request to join. + PartyAccept party_accept = 40; + // Kick a party member, or decline a request to join. + PartyRemove party_remove = 41; + // End a party, kicking all party members and closing it. + PartyClose party_close = 42; + // Request a list of pending join requests for a party. + PartyJoinRequestList party_join_request_list = 43; + // Incoming notification for one or more new presences attempting to join the party. + PartyJoinRequest party_join_request = 44; + // Begin matchmaking as a party. + PartyMatchmakerAdd party_matchmaker_add = 45; + // Cancel a party matchmaking process using a ticket. + PartyMatchmakerRemove party_matchmaker_remove = 46; + // A response from starting a new party matchmaking process. + PartyMatchmakerTicket party_matchmaker_ticket = 47; + // Incoming party data delivered from the server. + PartyData party_data = 48; + // A client to server request to send data to a party. + PartyDataSend party_data_send = 49; + // Presence update for a particular party. + PartyPresenceEvent party_presence_event = 50; + // Update Party label and whether it's open or closed. + PartyUpdate party_update = 51; + } +} + +// A user session associated to a stream, usually through a list operation or a join/leave event. +message UserPresence { + // The user this presence belongs to. + string user_id = 1; + // A unique session ID identifying the particular connection, because the user may have many. + string session_id = 2; + // The username for display purposes. + string username = 3; + // Whether this presence generates persistent data/messages, if applicable for the stream type. + bool persistence = 4; + // A user-set status message for this stream, if applicable. + google.protobuf.StringValue status = 5; +} + +// A realtime chat channel. +message Channel { + // The ID of the channel. + string id = 1; + // The users currently in the channel. + repeated UserPresence presences = 2; + // A reference to the current user's presence in the channel. + UserPresence self = 3; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 4; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 5; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 6; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 7; +} + +// Join operation for a realtime chat channel. +message ChannelJoin { + // The type of chat channel. + enum Type { + // Default case. Assumed as ROOM type. + TYPE_UNSPECIFIED = 0; + // A room which anyone can join to chat. + ROOM = 1; + // A private channel for 1-on-1 chat. + DIRECT_MESSAGE = 2; + // A channel for group chat. + GROUP = 3; + } + + // The user ID to DM with, group ID to chat with, or room channel name to join. + string target = 1; + // The type of the chat channel. + int32 type = 2; // one of "ChannelId.Type". + // Whether messages sent on this channel should be persistent. + google.protobuf.BoolValue persistence = 3; + // Whether the user should appear in the channel's presence list and events. + google.protobuf.BoolValue hidden = 4; +} + +// Leave a realtime channel. +message ChannelLeave { + // The ID of the channel to leave. + string channel_id = 1; +} + +// A receipt reply from a channel message send operation. +message ChannelMessageAck { + // The channel the message was sent to. + string channel_id = 1; + // The unique ID assigned to the message. + string message_id = 2; + // The code representing a message type or category. + google.protobuf.Int32Value code = 3; + // Username of the message sender. + string username = 4; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + google.protobuf.Timestamp create_time = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + google.protobuf.Timestamp update_time = 6; + // True if the message was persisted to the channel's history, false otherwise. + google.protobuf.BoolValue persistent = 7; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 8; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 9; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 10; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 11; +} + +// Send a message to a realtime channel. +message ChannelMessageSend { + // The channel to sent to. + string channel_id = 1; + // Message content. + string content = 2; +} + +// Update a message previously sent to a realtime channel. +message ChannelMessageUpdate { + // The channel the message was sent to. + string channel_id = 1; + // The ID assigned to the message to update. + string message_id = 2; + // New message content. + string content = 3; +} + +// Remove a message previously sent to a realtime channel. +message ChannelMessageRemove { + // The channel the message was sent to. + string channel_id = 1; + // The ID assigned to the message to update. + string message_id = 2; +} + +// A set of joins and leaves on a particular channel. +message ChannelPresenceEvent { + // The channel identifier this event is for. + string channel_id = 1; + // Presences joining the channel as part of this event, if any. + repeated UserPresence joins = 2; + // Presences leaving the channel as part of this event, if any. + repeated UserPresence leaves = 3; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 4; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 5; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 6; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 7; +} + +// A logical error which may occur on the server. +message Error { + // The selection of possible error codes. + enum Code { + // An unexpected result from the server. + RUNTIME_EXCEPTION = 0; + // The server received a message which is not recognised. + UNRECOGNIZED_PAYLOAD = 1; + // A message was expected but contains no content. + MISSING_PAYLOAD = 2; + // Fields in the message have an invalid format. + BAD_INPUT = 3; + // The match id was not found. + MATCH_NOT_FOUND = 4; + // The match join was rejected. + MATCH_JOIN_REJECTED = 5; + // The runtime function does not exist on the server. + RUNTIME_FUNCTION_NOT_FOUND = 6; + // The runtime function executed with an error. + RUNTIME_FUNCTION_EXCEPTION = 7; + } + + // The error code which should be one of "Error.Code" enums. + int32 code = 1; + // A message in English to help developers debug the response. + string message = 2; + // Additional error details which may be different for each response. + map context = 3; +} + +// A realtime match. +message Match { + // The match unique ID. + string match_id = 1; + // True if it's an server-managed authoritative match, false otherwise. + bool authoritative = 2; + // Match label, if any. + google.protobuf.StringValue label = 3; + // The number of users currently in the match. + int32 size = 4; + // The users currently in the match. + repeated UserPresence presences = 5; + // A reference to the current user's presence in the match. + UserPresence self = 6; +} + +// Create a new realtime match. +message MatchCreate { + // Optional name to use when creating the match. + string name = 1; +} + +// Realtime match data received from the server. +message MatchData { + // The match unique ID. + string match_id = 1; + // A reference to the user presence that sent this data, if any. + UserPresence presence = 2; + // Op code value. + int64 op_code = 3; + // Data payload, if any. + bytes data = 4; + // True if this data was delivered reliably, false otherwise. + bool reliable = 5; +} + +// Send realtime match data to the server. +message MatchDataSend { + // The match unique ID. + string match_id = 1; + // Op code value. + int64 op_code = 2; + // Data payload, if any. + bytes data = 3; + // List of presences in the match to deliver to, if filtering is required. Otherwise deliver to everyone in the match. + repeated UserPresence presences = 4; + // True if the data should be sent reliably, false otherwise. + bool reliable = 5; +} + +// Join an existing realtime match. +message MatchJoin { + oneof id { + // The match unique ID. + string match_id = 1; + // A matchmaking result token. + string token = 2; + } + // An optional set of key-value metadata pairs to be passed to the match handler, if any. + map metadata = 3; +} + +// Leave a realtime match. +message MatchLeave { + // The match unique ID. + string match_id = 1; +} + +// A set of joins and leaves on a particular realtime match. +message MatchPresenceEvent { + // The match unique ID. + string match_id = 1; + // User presences that have just joined the match. + repeated UserPresence joins = 2; + // User presences that have just left the match. + repeated UserPresence leaves = 3; +} + +// Start a new matchmaking process. +message MatchmakerAdd { + // Minimum total user count to match together. + int32 min_count = 1; + // Maximum total user count to match together. + int32 max_count = 2; + // Filter query used to identify suitable users. + string query = 3; + // String properties. + map string_properties = 4; + // Numeric properties. + map numeric_properties = 5; + // Optional multiple of the count that must be satisfied. + google.protobuf.Int32Value count_multiple = 6; +} + +message MatchmakerMatched_MatchmakerUser { + // User info. + UserPresence presence = 1; + // Party identifier, if this user was matched as a party member. + string party_id = 2; + // String properties. + map string_properties = 5; + // Numeric properties. + map numeric_properties = 6; +} + +// A successful matchmaking result. +message MatchmakerMatched { + // The matchmaking ticket that has completed. + string ticket = 1; + // The match token or match ID to join. + oneof id { + // Match ID. + string match_id = 2; + // Match join token. + string token = 3; + } + // The users that have been matched together, and information about their matchmaking data. + repeated MatchmakerMatched_MatchmakerUser users = 4; + // A reference to the current user and their properties. + MatchmakerMatched_MatchmakerUser self = 5; +} + +// Cancel an existing ongoing matchmaking process. +message MatchmakerRemove { + // The ticket to cancel. + string ticket = 1; +} + +// A ticket representing a new matchmaking process. +message MatchmakerTicket { + // The ticket that can be used to cancel matchmaking. + string ticket = 1; +} + +// A collection of zero or more notifications. +message Notifications { + // Collection of notifications. + repeated api.Notification notifications = 1; +} + +// Incoming information about a party. +message Party { + // Unique party identifier. + string party_id = 1; + // Open flag. + bool open = 2; + // Hidden flag. + bool hidden = 3; + // Maximum number of party members. + int32 max_size = 4; + // Self. + UserPresence self = 5; + // Leader. + UserPresence leader = 6; + // All current party members. + repeated UserPresence presences = 7; + // Label for party listing. + string label = 8; +} + +// Create a party. +message PartyCreate { + // Whether or not the party will require join requests to be approved by the party leader. + bool open = 1; + // Maximum number of party members. + int32 max_size = 2; + // Label + string label = 3; + // Whether the party is visible in party listings. + bool hidden = 4; +} + +// Update a party label. +message PartyUpdate { + // Party ID. + string party_id = 1; + // Label to set. + string label = 2; + // Change the party to open or closed. + bool open = 3; + // Whether the party is visible in party listings. + bool hidden = 4; +} + +// Join a party, or request to join if the party is not open. +message PartyJoin { + // Party ID to join. + string party_id = 1; +} + +// Leave a party. +message PartyLeave { + // Party ID to leave. + string party_id = 1; +} + +// Promote a new party leader. +message PartyPromote { + // Party ID to promote a new leader for. + string party_id = 1; + // The presence of an existing party member to promote as the new leader. + UserPresence presence = 2; +} + +// Announcement of a new party leader. +message PartyLeader { + // Party ID to announce the new leader for. + string party_id = 1; + // The presence of the new party leader. + UserPresence presence = 2; +} + +// Accept a request to join. +message PartyAccept { + // Party ID to accept a join request for. + string party_id = 1; + // The presence to accept as a party member. + UserPresence presence = 2; +} + +// Kick a party member, or decline a request to join. +message PartyRemove { + // Party ID to remove/reject from. + string party_id = 1; + // The presence to remove or reject. + UserPresence presence = 2; +} + +// End a party, kicking all party members and closing it. +message PartyClose { + // Party ID to close. + string party_id = 1; +} + +// Request a list of pending join requests for a party. +message PartyJoinRequestList { + // Party ID to get a list of join requests for. + string party_id = 1; +} + +// Incoming notification for one or more new presences attempting to join the party. +message PartyJoinRequest { + // Party ID these presences are attempting to join. + string party_id = 1; + // Presences attempting to join. + repeated UserPresence presences = 2; +} + +// Begin matchmaking as a party. +message PartyMatchmakerAdd { + // Party ID. + string party_id = 1; + // Minimum total user count to match together. + int32 min_count = 2; + // Maximum total user count to match together. + int32 max_count = 3; + // Filter query used to identify suitable users. + string query = 4; + // String properties. + map string_properties = 5; + // Numeric properties. + map numeric_properties = 6; + // Optional multiple of the count that must be satisfied. + google.protobuf.Int32Value count_multiple = 7; +} + +// Cancel a party matchmaking process using a ticket. +message PartyMatchmakerRemove { + // Party ID. + string party_id = 1; + // The ticket to cancel. + string ticket = 2; +} + +// A response from starting a new party matchmaking process. +message PartyMatchmakerTicket { + // Party ID. + string party_id = 1; + // The ticket that can be used to cancel matchmaking. + string ticket = 2; +} + +// Incoming party data delivered from the server. +message PartyData { + // The party ID. + string party_id = 1; + // A reference to the user presence that sent this data, if any. + UserPresence presence = 2; + // Op code value. + int64 op_code = 3; + // Data payload, if any. + bytes data = 4; +} + +// Send data to a party. +message PartyDataSend { + // Party ID to send to. + string party_id = 1; + // Op code value. + int64 op_code = 2; + // Data payload, if any. + bytes data = 3; +} + +// Presence update for a particular party. +message PartyPresenceEvent { + // The party ID. + string party_id = 1; + // User presences that have just joined the party. + repeated UserPresence joins = 2; + // User presences that have just left the party. + repeated UserPresence leaves = 3; +} + + +// Application-level heartbeat and connection check. +message Ping {} + +// Application-level heartbeat and connection check response. +message Pong {} + +// A snapshot of statuses for some set of users. +message Status { + // User statuses. + repeated UserPresence presences = 1; +} + +// Start receiving status updates for some set of users. +message StatusFollow { + // User IDs to follow. + repeated string user_ids = 1; + // Usernames to follow. + repeated string usernames = 2; +} + +// A batch of status updates for a given user. +message StatusPresenceEvent { + // New statuses for the user. + repeated UserPresence joins = 2; + // Previous statuses for the user. + repeated UserPresence leaves = 3; +} + +// Stop receiving status updates for some set of users. +message StatusUnfollow { + // Users to unfollow. + repeated string user_ids = 1; +} + +// Set the user's own status. +message StatusUpdate { + // Status string to set, if not present the user will appear offline. + google.protobuf.StringValue status = 1; +} + +// Represents identifying information for a stream. +message Stream { + // Mode identifies the type of stream. + int32 mode = 1; + // Subject is the primary identifier, if any. + string subject = 2; + // Subcontext is a secondary identifier, if any. + string subcontext = 3; + // The label is an arbitrary identifying string, if the stream has one. + string label = 4; +} + +// A data message delivered over a stream. +message StreamData { + // The stream this data message relates to. + Stream stream = 1; + // The sender, if any. + UserPresence sender = 2; + // Arbitrary contents of the data message. + string data = 3; + // True if this data was delivered reliably, false otherwise. + bool reliable = 4; +} + +// A set of joins and leaves on a particular stream. +message StreamPresenceEvent { + // The stream this event relates to. + Stream stream = 1; + // Presences joining the stream as part of this event, if any. + repeated UserPresence joins = 2; + // Presences leaving the stream as part of this event, if any. + repeated UserPresence leaves = 3; +} + diff --git a/cmd/codegen-modular/protos/satori-api.proto b/cmd/codegen-modular/protos/satori-api.proto new file mode 100644 index 000000000..38c108ca5 --- /dev/null +++ b/cmd/codegen-modular/protos/satori-api.proto @@ -0,0 +1,229 @@ +// Copyright 2022 GameUp Online, Inc. d/b/a Heroic Labs +// +// NOTICE: All information contained herein is, and remains the property of Heroic +// Labs. and its suppliers, if any. The intellectual and technical concepts contained +// herein are proprietary to Heroic Labs. and its suppliers and may be covered by U.S. +// and Foreign Patents, patents in process, and are protected by trade secret or +// copyright law. Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained from Heroic Labs. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/heroiclabs/satori/api"; + +option java_multiple_files = true; +option java_outer_classname = "SatoriApi"; +option java_package = "com.heroiclabs.satori.api"; + +/** + * The public client API for the Satori server. + */ +package satori.api; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "Satori Server API"; + version: "1.0"; + contact: { + name: "Heroic Labs"; + url: "https://heroiclabs.com"; + email: "hello@heroiclabs.com"; + }; + }; + host: "127.0.0.1:7450"; + schemes: HTTP; + consumes: "application/json"; + produces: "application/json"; + security_definitions: { + security: { + key: "BasicAuth"; + value: { + type: TYPE_BASIC; + } + } + security: { + // Made up security so we can apply "Bearer " + key: "BearerJwt"; + value: {}; + } + } + // Default security definition. + security: { + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } +}; + +/** + * The GRPC protocol service for Satori built with GRPC. + */ +service Satori { + // Authenticate against the server. + rpc Authenticate (AuthenticateRequest) returns (Session) { + option (google.api.http) = { + post: "/v1/authenticate", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + rpc AuthenticateLogout (AuthenticateLogoutRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/authenticate/logout", + body: "*" + }; + } + + // Refresh a user's session using a refresh token retrieved from a previous authentication request. + rpc AuthenticateRefresh (AuthenticateRefreshRequest) returns (Session) { + option (google.api.http) = { + post: "/v1/authenticate/refresh", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Delete the caller's identity and associated data. + rpc DeleteIdentity(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v1/identity"; + } + + // Publish an event for this session. + rpc Event(EventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/event", + body: "*" + }; + } + + // Publish server events for multiple distinct identities. + rpc ServerEvent(EventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/server-event", + body: "*" + }; + } + + // Get or list all available experiments for this identity. + rpc GetExperiments (GetExperimentsRequest) returns (ExperimentList) { + option (google.api.http).get = "/v1/experiment"; + } + + // List all available flags and their value overrides for this identity. + rpc GetFlagOverrides (GetFlagsRequest) returns (FlagOverrideList) { + option (google.api.http).get = "/v1/flag/override"; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // List all available flags for this identity. + rpc GetFlags (GetFlagsRequest) returns (FlagList) { + option (google.api.http).get = "/v1/flag"; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // List available live events. + rpc GetLiveEvents (GetLiveEventsRequest) returns (LiveEventList) { + option (google.api.http).get = "/v1/live-event"; + } + + // Join an 'explicit join' live event. + rpc JoinLiveEvent(JoinLiveEventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/live-event/{id}/participation" + }; + } + + // A healthcheck which load balancers can use to check the service. + rpc Healthcheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/healthcheck"; + } + + // Enrich/replace the current session with new identifier. + rpc Identify(IdentifyRequest) returns (Session) { + option (google.api.http) = { + put: "/v1/identify", + body: "*" + }; + } + + // List properties associated with this identity. + rpc ListProperties (google.protobuf.Empty) returns (Properties) { + option (google.api.http).get = "/v1/properties"; + } + + // A readycheck which load balancers can use to check the service. + rpc Readycheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/readycheck"; + } + + // Update identity properties. + rpc UpdateProperties(UpdatePropertiesRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v1/properties", + body: "*" + }; + } + + // Get the list of messages for the identity. + rpc GetMessageList (GetMessageListRequest) returns (GetMessageListResponse) { + option (google.api.http).get = "/v1/message"; + } + + // Updates a message for an identity. + rpc UpdateMessage (UpdateMessageRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v1/message/{id}", + body: "*" + }; + } + + // Deletes a message for an identity. + rpc DeleteMessage (DeleteMessageRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v1/message/{id}"; + } +} diff --git a/cmd/codegen-modular/protos/satori-types.proto b/cmd/codegen-modular/protos/satori-types.proto new file mode 100644 index 000000000..155da3cd9 --- /dev/null +++ b/cmd/codegen-modular/protos/satori-types.proto @@ -0,0 +1,368 @@ +// Copyright 2022 GameUp Online, Inc. d/b/a Heroic Labs +// +// NOTICE: All information contained herein is, and remains the property of Heroic +// Labs. and its suppliers, if any. The intellectual and technical concepts contained +// herein are proprietary to Heroic Labs. and its suppliers and may be covered by U.S. +// and Foreign Patents, patents in process, and are protected by trade secret or +// copyright law. Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained from Heroic Labs. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "github.com/heroiclabs/satori/api"; + +option java_multiple_files = true; +option java_outer_classname = "SatoriApi"; +option java_package = "com.heroiclabs.satori.api"; + +package satori.api; + +// Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. +message AuthenticateLogoutRequest { + // Session token to log out. + string token = 1; + // Refresh token to invalidate. + string refresh_token = 2; +} + +// Authenticate against the server with a refresh token. +message AuthenticateRefreshRequest { + // Refresh token. + string refresh_token = 1; +} + +// Authentication request +message AuthenticateRequest { + // Identity ID. Must be between eight and 128 characters (inclusive). + // Must be an alphanumeric string with only underscores and hyphens allowed. + string id = 1; + // Optional default properties to update with this call. + // If not set, properties are left as they are on the server. + map default = 2; + // Optional custom properties to update with this call. + // If not set, properties are left as they are on the server. + map custom = 3; + // Optional no_session modifies the request to only create/update + // an identity without creating a new session. If set to 'true' + // the response won't include a token and a refresh token. + bool no_session = 4; +} + +// The request to delete a scheduled message. +message DeleteMessageRequest { + // The identifier of the message. + string id = 1; +} + +// A single event. Usually, but not necessarily, part of a batch. +message Event { + // Event name. + string name = 1; + // Optional event ID assigned by the client, used to de-duplicate in retransmission scenarios. + // If not supplied the server will assign a randomly generated unique event identifier. + string id = 2; + // Event metadata, if any. + map metadata = 3; + // Optional value. + string value = 4; + // The time when the event was triggered on the producer side. + google.protobuf.Timestamp timestamp = 5; + // The identity id associated with the event. Ignored if the event is published as part of a session. + string identity_id = 6; + // The session id associated with the event. Ignored if the event is published as part of a session. + string session_id = 7; + // The session issued at associated with the event. Ignored if the event is published as part of a session. + int64 session_issued_at = 8; + // The session expires at associated with the event. Ignored if the event is published as part of a session. + int64 session_expires_at = 9; +} + +// Publish an event to the server +message EventRequest { + // Some number of events produced by a client. + repeated Event events = 1; +} + +// An experiment that this user is partaking. +message Experiment { + // Experiment name + string name = 1; + // Value associated with this Experiment. + string value = 2; + // The labels associated with this experiment. + repeated string labels = 3; + // Experiment Phase name + string phase_name = 4; + // Experiment Phase Variant name + string phase_variant_name = 5; +} + +// All experiments that this identity is involved with. +message ExperimentList { + // All experiments for this identity. + repeated Experiment experiments = 1; +} + +// The type of configuration that overrides a flag value. +enum ValueChangeReasonType { + VCR_UNKNOWN = 0; + VCR_FLAG_VARIANT = 1; + VCR_LIVE_EVENT = 2; + VCR_EXPERIMENT = 3; +} + +// The origin of change on a flag value. +message ValueChangeReason { + // The type of the configuration that declared the override. + ValueChangeReasonType type = 1; + // The name of the configuration that overrides the flag value. + string name = 2; + // The variant name of the configuration that overrides the flag value. + string variant_name = 3; +} + +// Feature flag available to the identity. +message Flag { + // Flag name + string name = 1; + // Value associated with this flag. + string value = 2; + // Whether the value for this flag has conditionally changed from the default state. + bool condition_changed = 3; + // The origin of change on the flag value returned. + ValueChangeReason change_reason = 4; + // The labels associated with this flag. + repeated string labels = 5; +} + +// All flags available to the identity +message FlagList { + // All flags + repeated Flag flags = 1; +} + +// The type of configuration that overrides a flag value. +enum FlagOverrideType { + FOT_FLAG = 0; + FOT_FLAG_VARIANT = 1; + FOT_LIVE_EVENT_FLAG = 2; + FOT_LIVE_EVENT_FLAG_VARIANT = 3; + FOT_EXPERIMENT_PHASE_VARIANT_FLAG = 4; +} + +// The details of a flag value override. +message FlagOverrideValue { + // The type of the configuration that declared the override. + FlagOverrideType type = 1; + // The name of the configuration that overrides the flag value. + string name = 2; + // The variant name of the configuration that overrides the flag value. + string variant_name = 3; + // The value of the configuration that overrides the flag. + string value = 4; + // The create time of the configuration that overrides the flag. + int64 create_time_sec = 5; +} + +// Feature flag available to the identity. +message FlagOverride { + // Flag name + string flag_name = 1; + // The list of configuration that affect the value of the flag. + repeated FlagOverrideValue overrides = 2; + // The labels associated with this flag. + repeated string labels = 3; +} + +// All flags available to the identity and their value overrides +message FlagOverrideList { + // All flags + repeated FlagOverride flags = 1; +} + +// Request to get all experiments data. +message GetExperimentsRequest { + // Experiment names; if empty string, all experiments are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Experiment; if empty string, all experiments are returned based on the remaining filters. + repeated string labels = 2; +} + +// Request to get all flags data. +message GetFlagsRequest { + // Flag names; if empty string, all flags are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Flag; if empty string, all flags are returned based on the remaining filters. + repeated string labels = 2; +} + +// Request to get all live events. +message GetLiveEventsRequest { + // Live event names; if empty string, all live events are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Live Event; if empty string, all live events are returned based on the remaining filters. + repeated string labels = 2; + // The maximum number of past event runs to return for each live event. + int32 past_run_count = 3; + // The maximum number of future event runs to return for each live event. + int32 future_run_count = 4; + // Start time of the time window filter to apply. + int64 start_time_sec = 5; + // End time of the time window filter to apply. + int64 end_time_sec = 6; +} + +// A scheduled message. +message Message { + // The identifier of the schedule. + string schedule_id = 1; + // The send time for the message. + int64 send_time = 2; + // A key-value pairs of metadata. + map metadata = 3; + // The time the message was created. + int64 create_time = 4; + // The time the message was updated. + int64 update_time = 5; + // The time the message was read by the client. + int64 read_time = 6; + // The time the message was consumed by the identity. + int64 consume_time = 7; + // The message's text. + string text = 8; + // The message's unique identifier. + string id = 9; + // The message's title. + string title = 10; + // The message's image url. + string image_url = 11; +} + +message GetMessageListRequest { + // Max number of messages to return. Between 1 and 100. + int32 limit = 1; + // True if listing should be older messages to newer, false if reverse. + bool forward = 2; + // A pagination cursor, if any. + string cursor = 3; + // A list of message identifiers to be returned. If these are given as inputs, the pagination will be disabled. + repeated string message_ids = 4; +} + +// A response containing all the messages for an identity. +message GetMessageListResponse { + // The list of messages. + repeated Message messages = 1; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; + // Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. + string cacheable_cursor = 4; +} + +// Enrich/replace the current session with a new ID. +message IdentifyRequest { + // Identity ID to enrich the current session and return a new session. Old session will no longer be usable. + string id = 1; + // Optional default properties to update with this call. + // If not set, properties are left as they are on the server. + map default = 2; + // Optional custom properties to update with this call. + // If not set, properties are left as they are on the server. + map custom = 3; +} + +// Request to join a 'explicit join' live event. +message JoinLiveEventRequest { + // Live event id to join. + string id = 1; +} + +// The status variants of a live event. +enum LiveEventStatus { + LES_UNKNOWN = 0; + LES_ACTIVE = 1; + LES_UPCOMING = 2; + LES_TERMINATED = 3; +} + +// A single live event. +message LiveEvent { + // Name. + string name = 1; + // Description. + string description = 2; + // Event value. + string value = 3; + // Start time of current event run. + int64 active_start_time_sec = 4; + // End time of current event run. + int64 active_end_time_sec = 5; + // The live event identifier. + string id = 6; + // Start time. + int64 start_time_sec = 7; + // End time, 0 if it repeats forever. + int64 end_time_sec = 8; + // Duration in seconds. + int64 duration_sec = 9; + // Reset CRON schedule, if configured. + string reset_cron = 10; + // The status of this live event run. + LiveEventStatus status = 11; + // The labels associated with this live event. + repeated string labels = 12; +} + +// List of Live events. +message LiveEventList { + // Live events. + repeated LiveEvent live_events = 1; + // Live events that require explicit join. + repeated LiveEvent explicit_join_live_events = 2; +} + +// Properties associated with an identity. +message Properties { + // Event default properties. + map default = 1; + // Event computed properties. + map computed = 2; + // Event custom properties. + map custom = 3; +} + +// A session. +message Session { + // Token credential. + string token = 1; + // Refresh token. + string refresh_token = 2; + // Properties associated with this identity. + Properties properties = 3; +} + +// Update Properties associated with this identity. +message UpdatePropertiesRequest { + // Event default properties. + map default = 1; + // Event custom properties. + map custom = 2; + // Informs the server to recompute the audience membership of the identity. + google.protobuf.BoolValue recompute = 3; +} + +// The request to update the status of a message. +message UpdateMessageRequest { + // The identifier of the messages. + string id = 1; + // The time the message was read at the client. + int64 read_time = 2; + // The time the message was consumed by the identity. + int64 consume_time = 3; +} diff --git a/cmd/codegen-modular/schema/api.go b/cmd/codegen-modular/schema/api.go new file mode 100644 index 000000000..f078d2fa4 --- /dev/null +++ b/cmd/codegen-modular/schema/api.go @@ -0,0 +1,200 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package schema + +import ( + "bytes" + "fmt" + "os" + "regexp" + "strings" + + "github.com/emicklei/proto" +) + +var commentBracketRegex = regexp.MustCompile(`\[(.*?)\]`) + +type Api struct { + // Use slices to preserve order of proto messages + Enums []*VisitedEnum + Messages []*VisitedMessage + Rpcs []*VisitedRpc + + EnumsByName map[string]*VisitedEnum + MessagesByName map[string]*VisitedMessage + RpcsByName map[string]*VisitedRpc +} + +func (api *Api) addFile(protoFile string) error { + fileBytes, err := os.ReadFile(protoFile) + if err != nil { + return fmt.Errorf("reading %s: %w", protoFile, err) + } + + parser := proto.NewParser(bytes.NewReader(fileBytes)) + parsedProto, err := parser.Parse() + if err != nil { + return fmt.Errorf("parsing %s: %w", protoFile, err) + } + + // proto.Walk callbacks can't return errors, so capture the first error here. + var walkErr error + + proto.Walk( + parsedProto, + proto.WithEnum( + func(enum *proto.Enum) { + if walkErr != nil { + return + } + + comment := "" + if enum.Comment != nil { + comment = enum.Comment.Message() + + // Extract descriptive text from proto comments like: + // // [The operator to combine properties.] ... + // The bracketed text becomes the enum's display comment. + // If multiple brackets exist, only the last match is kept. + matches := commentBracketRegex.FindAllStringSubmatch(comment, -1) + for _, match := range matches { + comment = match[1] + } + } + + // Build fully qualified enum name including parent message if nested + enumName := enum.Name + if enum.Parent != nil { + if parentMsg, ok := enum.Parent.(*proto.Message); ok { + enumName = parentMsg.Name + "_" + enum.Name + } + } + + visitor := &enumVisitor{ + Enum: &VisitedEnum{ + Comment: strings.Trim(comment, " "), + Fields: make([]*EnumField, 0), + Name: enumName, + }, + } + for _, each := range enum.Elements { + each.Accept(visitor) + } + api.Enums = append(api.Enums, visitor.Enum) + api.EnumsByName[visitor.Enum.Name] = visitor.Enum + }, + ), + proto.WithMessage( + func(message *proto.Message) { + if walkErr != nil { + return + } + + if message.Name == "google.protobuf.EnumValueOptions" { + return + } + + comment := "" + if message.Comment != nil { + comment = message.Comment.Message() + } + visitor := &messageVisitor{ + Message: &VisitedMessage{ + Comment: comment, + Fields: make([]*proto.NormalField, 0), + MapFields: make([]*proto.MapField, 0), + Name: message.Name, + }, + } + for _, each := range message.Elements { + each.Accept(visitor) + } + api.Messages = append(api.Messages, visitor.Message) + api.MessagesByName[visitor.Message.Name] = visitor.Message + }, + ), + proto.WithRPC( + func(rpc *proto.RPC) { + if walkErr != nil { + return + } + + comment := "" + if rpc.Comment != nil { + comment = rpc.Comment.Message() + comment = strings.TrimSpace(comment) + } + + resolveType := func(fullTypeName string) *VisitedMessage { + if fullTypeName == "google.protobuf.Empty" { + return nil + } + // We get something like `api.MyRequestType`, so strip after the last dot. + stripped := fullTypeName[strings.LastIndex(fullTypeName, ".")+1:] + + t, ok := api.MessagesByName[stripped] + if !ok { + walkErr = fmt.Errorf("unable to find type %s for RPC %s", fullTypeName, rpc.Name) + return nil + } + return t + } + + requestType := resolveType(rpc.RequestType) + returnType := resolveType(rpc.ReturnsType) + if walkErr != nil { + return + } + + visitor := &rpcVisitor{ + Rpc: &VisitedRpc{ + Comment: comment, + RequestType: requestType, + ReturnType: returnType, + Name: rpc.Name, + }, + } + + for _, each := range rpc.Elements { + each.Accept(visitor) + } + api.Rpcs = append(api.Rpcs, visitor.Rpc) + api.RpcsByName[visitor.Rpc.Name] = visitor.Rpc + }, + ), + ) + + return walkErr +} + +func LoadApi(protoFiles []string) (Api, error) { + api := Api{ + Enums: make([]*VisitedEnum, 0), + Messages: make([]*VisitedMessage, 0), + Rpcs: make([]*VisitedRpc, 0), + + EnumsByName: make(map[string]*VisitedEnum, 0), + MessagesByName: make(map[string]*VisitedMessage, 0), + RpcsByName: make(map[string]*VisitedRpc, 0), + } + + // Load file by file... + // Updates maps internally, so each subsequent + // file will have previous context to work with. + for _, f := range protoFiles { + if err := api.addFile(f); err != nil { + return Api{}, fmt.Errorf("loading %s: %w", f, err) + } + } + + return api, nil +} diff --git a/cmd/codegen-modular/schema/visitors.go b/cmd/codegen-modular/schema/visitors.go new file mode 100644 index 000000000..6222093c7 --- /dev/null +++ b/cmd/codegen-modular/schema/visitors.go @@ -0,0 +1,184 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package schema + +import ( + "regexp" + "strings" + "text/scanner" + + "github.com/emicklei/proto" +) + +var endpointParamRegex = regexp.MustCompile(`\{([a-zA-Z0-9_]*)\}`) + +// -------------------- +// Enums +type EnumField struct { + *proto.EnumField + Input string + Output string +} + +type VisitedEnum struct { + Comment string + Fields []*EnumField + Name string +} + +type enumVisitor struct { + proto.NoopVisitor + Enum *VisitedEnum +} + +func (v *enumVisitor) VisitEnumField(ef *proto.EnumField) { + if ef.Comment == nil { + ef.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + var input, output string + for _, each := range ef.Elements { + option, ok := each.(*proto.Option) + if ok { + if strings.Contains(option.Name, "input") { + input = option.Constant.Source + } else if strings.Contains(option.Name, "output") { + output = option.Constant.Source + } + } + } + + v.Enum.Fields = append(v.Enum.Fields, &EnumField{ + EnumField: ef, + Input: input, + Output: output, + }) +} + +// -------------------- +// Messages +type VisitedMessage struct { + Comment string + Fields []*proto.NormalField + MapFields []*proto.MapField + OneofFields []*proto.OneOfField + Name string +} + +type messageVisitor struct { + proto.NoopVisitor + Message *VisitedMessage +} + +func (v *messageVisitor) VisitMapField(mf *proto.MapField) { + if mf.Comment == nil { + mf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.MapFields = append(v.Message.MapFields, mf) +} + +func (v *messageVisitor) VisitNormalField(nf *proto.NormalField) { + if nf.Comment == nil { + nf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.Fields = append(v.Message.Fields, nf) +} + +func (v *messageVisitor) VisitOneof(oneof *proto.Oneof) { + for _, o := range oneof.Elements { + o.Accept(v) + } +} + +func (v *messageVisitor) VisitOneofField(oneof *proto.OneOfField) { + oneof.Name = oneof.Name[strings.LastIndex(oneof.Name, ".")+1:] + v.Message.OneofFields = append(v.Message.OneofFields, oneof) +} + +// -------------------- +// RPCs +type VisitedRpc struct { + Name string + Comment string + RequestType *VisitedMessage + ReturnType *VisitedMessage + Endpoint string + Method string + PathParams []string // Parameter names extracted from endpoint URL patterns like /v2/group/{group_id} + BodyField string // The field name to use as the body (from google.api.http body option) +} + +type rpcVisitor struct { + proto.NoopVisitor + Rpc *VisitedRpc +} + +func tryGetHttpMethod(str string) (string, bool) { + if str == "post" || strings.HasSuffix(str, ".post") { + return "POST", true + } + if str == "get" || strings.HasSuffix(str, ".get") { + return "GET", true + } + if str == "delete" || strings.HasSuffix(str, ".delete") { + return "DELETE", true + } + if str == "put" || strings.HasSuffix(str, ".put") { + return "PUT", true + } + + return "", false +} + +func (v *rpcVisitor) VisitOption(o *proto.Option) { + if o.Constant.IsString { + v.Rpc.Endpoint = o.Constant.Source + } + + // + // Sometimes we have an option like (google.api.http).post + method, found := tryGetHttpMethod(o.Name) + if found { + v.Rpc.Method = method + } + + // + // And sometimes the method is hidden inside option props. + if o.Name == "(google.api.http)" { + for _, l := range o.Constant.OrderedMap { + method, found := tryGetHttpMethod(l.Name) + if found { + v.Rpc.Method = method + v.Rpc.Endpoint = l.Literal.Source + } + // Extract the body field specification + if l.Name == "body" { + v.Rpc.BodyField = l.Literal.Source + } + } + } + + matches := endpointParamRegex.FindAllStringSubmatch(v.Rpc.Endpoint, -1) + + v.Rpc.PathParams = make([]string, 0) + for _, m := range matches { + v.Rpc.PathParams = append(v.Rpc.PathParams, m[1]) + } +} diff --git a/cmd/codegen-modular/unreal-sdk/api_mapper.go b/cmd/codegen-modular/unreal-sdk/api_mapper.go new file mode 100644 index 000000000..0cff7d4c6 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/api_mapper.go @@ -0,0 +1,353 @@ +package main + +import ( + "fmt" + "log" + "strings" + + "heroiclabs.com/yacg" + "heroiclabs.com/yacg/modules" +) + +// Implements HttpApiMapper +type UnrealHttpApiMapper struct{} + +func (m UnrealHttpApiMapper) MapApi(api yacg.Api, nameResolver modules.NameResolver) (modules.ApiMap, error) { + enums := make([]modules.Enum, 0, len(api.Enums)) + for _, enum := range api.Enums { + e, err := m.MapEnum(enum, api, nameResolver) + if err != nil { + return modules.ApiMap{}, err + } + enums = append(enums, e) + } + + types := make([]modules.Type, 0, len(api.Messages)) + for _, message := range api.Messages { + m, err := m.MapMessage(message, api, nameResolver) + if err != nil { + return modules.ApiMap{}, err + } + types = append(types, m) + } + + funcs := make([]modules.Function, 0, len(api.Rpcs)) + for _, rpc := range api.Rpcs { + f, err := m.MapRpc(rpc, api, nameResolver) + if err != nil { + return modules.ApiMap{}, err + } + funcs = append(funcs, f...) + } + + return modules.ApiMap{Enums: enums, Funcs: funcs}, nil +} + +func (m UnrealHttpApiMapper) MapEnum(enum *yacg.ProtoEnum, api yacg.Api, nameResolver modules.NameResolver) (modules.Enum, error) { + values := make(map[string]modules.EnumValue, len(enum.Fields)) + for _, f := range enum.Fields { + values[f.Name] = modules.EnumValue{ + Value: f.Integer, + Comment: f.Comment.Message(), + } + } + + return modules.Enum{ + DataDecl: modules.DataDecl{ + Name: enum.Name, + Comment: enum.Comment, + Type: nameResolver.Resolve("int", modules.EnumType), + }, + Values: values, + }, nil +} + +type PathParam struct { + Name string // Like GroupId + PathName string // Like `group_id` +} + +type QueryParam struct { + Repeated bool // Whether or not this is a collection + IterationType string // For repeated collections, a singular type we iterate over + Name string // Like QueryParam + QueryFormat string // Like %d + QueryValueSetter string // None or FGenericPlatformHttp::UrlEncode +} + +type BodyParam struct { + Name string + Repeated bool + IsMap bool + IterationType string + JsonArrayType string + MaybeToJson string // "" or ".ToJson()" + JsonFieldName string + JsonSetter string +} + +func makeFuncLocals(rpc *yacg.ProtoRpc, nameResolver modules.NameResolver, authType string, authKey string) map[string]any { + funcLocals := make(map[string]any, 0) + funcLocals["Endpoint"] = rpc.Endpoint + funcLocals["Method"] = rpc.Method + + if rpc.RequestType == nil { + if len(rpc.PathParams) != 0 || len(rpc.QueryParams) != 0 || len(rpc.BodyParams) != 0 { + log.Fatalf("Request has parameters, but rpc.RequestType is nil.") + } + } + + queryParams := make([]QueryParam, 0, len(rpc.QueryParams)) + pathParams := make([]PathParam, 0, len(rpc.PathParams)) + bodyParams := make([]BodyParam, 0, len(rpc.BodyParams)) + for _, qp := range rpc.QueryParams { + for _, f := range rpc.RequestType.Fields { + if f.Name == qp { + queryParams = append(queryParams, QueryParam{ + Repeated: f.Repeated, + IterationType: nameResolver.Resolve(f.Type, modules.Param), + Name: nameResolver.Resolve(qp, modules.Param), + QueryFormat: nameResolver.Resolve(f.Type, modules.QueryFormat), + QueryValueSetter: nameResolver.Resolve(f.Type, QueryValueSetter), + }) + } + } + for _, f := range rpc.RequestType.MapFields { + if f.Name == qp { + queryParams = append(queryParams, QueryParam{ + Repeated: false, + Name: nameResolver.Resolve(qp, modules.Param), + QueryFormat: nameResolver.Resolve(f.Type, modules.QueryFormat), + QueryValueSetter: nameResolver.Resolve(f.Type, QueryValueSetter), + }) + } + } + for _, f := range rpc.RequestType.OneofFields { + if f.Name == qp { + queryParams = append(queryParams, QueryParam{ + Repeated: false, + Name: nameResolver.Resolve(qp, modules.Param), + QueryFormat: nameResolver.Resolve(f.Type, modules.QueryFormat), + QueryValueSetter: nameResolver.Resolve(f.Type, QueryValueSetter), + }) + } + } + } + for _, pp := range rpc.PathParams { + for _, f := range rpc.RequestType.Fields { + if f.Name == pp { + pathParams = append(pathParams, PathParam{ + PathName: pp, + Name: nameResolver.Resolve(pp, modules.FieldName), + }) + } + } + for _, f := range rpc.RequestType.MapFields { + if f.Name == pp { + pathParams = append(pathParams, PathParam{ + PathName: pp, + Name: nameResolver.Resolve(pp, modules.FieldName), + }) + } + } + for _, f := range rpc.RequestType.OneofFields { + if f.Name == pp { + pathParams = append(pathParams, PathParam{ + PathName: pp, + Name: nameResolver.Resolve(pp, modules.FieldName), + }) + } + } + } + for _, bp := range rpc.BodyParams { + for _, f := range rpc.RequestType.Fields { + if f.Name == bp { + bodyParams = append(bodyParams, BodyParam{ + Name: nameResolver.Resolve(bp, modules.FieldName), + Repeated: f.Repeated, + IsMap: false, + IterationType: nameResolver.Resolve(f.Type, modules.Param), + JsonArrayType: nameResolver.Resolve(f.Type, modules.JsonArrayType), + MaybeToJson: nameResolver.Resolve(f.Type, MaybeToJson), + JsonFieldName: bp, + JsonSetter: nameResolver.Resolve(f.Type, modules.JsonSetter), + }) + } + } + for _, f := range rpc.RequestType.MapFields { + if f.Name == bp { + bodyParams = append(bodyParams, BodyParam{ + Name: nameResolver.Resolve(bp, modules.FieldName), + Repeated: false, + IsMap: true, + IterationType: nameResolver.Resolve(f.Type, modules.Param), + JsonArrayType: nameResolver.Resolve(f.Type, modules.JsonArrayType), + MaybeToJson: nameResolver.Resolve(f.Type, MaybeToJson), + JsonFieldName: bp, + JsonSetter: nameResolver.Resolve(f.Type, modules.JsonSetter), + }) + } + } + for _, f := range rpc.RequestType.OneofFields { + if f.Name == bp { + bodyParams = append(bodyParams, BodyParam{ + Name: nameResolver.Resolve(bp, modules.FieldName), + Repeated: false, + IsMap: false, + IterationType: nameResolver.Resolve(f.Type, modules.Param), + JsonArrayType: nameResolver.Resolve(f.Type, modules.JsonArrayType), + MaybeToJson: nameResolver.Resolve(f.Type, MaybeToJson), + JsonFieldName: bp, + JsonSetter: nameResolver.Resolve(f.Type, modules.JsonSetter), + }) + } + } + } + + funcLocals["QueryParams"] = queryParams + funcLocals["PathParams"] = pathParams + funcLocals["BodyParams"] = bodyParams + + funcLocals["ReturnTypeName"] = "" + funcLocals["SuccessLambdaType"] = "" + if rpc.ReturnType != nil { + funcLocals["ReturnTypeName"] = nameResolver.Resolve(rpc.ReturnType.Name, FuncReturnTypeName) + funcLocals["SuccessLambdaType"] = nameResolver.Resolve(rpc.ReturnType.Name, SuccessLambdaType) + } + + funcLocals["AuthType"] = authType // Basic | HttpKey | Bearer + funcLocals["AuthKey"] = authKey // TEXT("") | HttpKey | Session.Token + return funcLocals +} + +func (m UnrealHttpApiMapper) MapRpc(rpc *yacg.ProtoRpc, api yacg.Api, nameResolver modules.NameResolver) ([]modules.Function, error) { + funcOverloads := make([]modules.Function, 0, 1) + + isAuth := strings.Contains(rpc.Name, "Authenticate") + isSessionRefresh := rpc.Name == "SessionRefresh" + needsSession := !isAuth && !isSessionRefresh + + funcReturnTypeName := "" + if rpc.ReturnType != nil { + funcReturnTypeName = nameResolver.Resolve(rpc.ReturnType.Name, FuncReturnTypeName) + } + funcDataDecl := modules.DataDecl{ + Name: nameResolver.Resolve(rpc.Name, modules.FuncName), + Comment: rpc.Comment, + Type: funcReturnTypeName, + } + + // + // Without a session, we have just one overload + if !needsSession { + paramsType, err := m.MapMessage(rpc.RequestType, api, nameResolver) + if err != nil { + return nil, err + } + + returns, err := m.MapMessage(rpc.ReturnType, api, nameResolver) + if err != nil { + return nil, err + } + + funcOverloads = append(funcOverloads, modules.Function{ + DataDecl: funcDataDecl, + Params: paramsType.Members, + ReturnType: returns, + Locals: makeFuncLocals(rpc, nameResolver, "Basic", "TEXT(\"\")"), + }) + + return funcOverloads, nil + } + + // + // If we need a session, there are two overloads: one with HttpKey, and other with session. + returns, err := m.MapMessage(rpc.ReturnType, api, nameResolver) + if err != nil { + return nil, err + } + + // HttpKey overload + { + params, err := m.MapMessage(rpc.RequestType, api, nameResolver) + if err != nil { + return nil, err + } + httpKeyParam := modules.DataDecl{ + Type: "const FString&", + Name: "HttpKey", + Comment: "HttpKey for server-to-server communication", + } + paramsMembers := append([]modules.DataDecl{httpKeyParam}, params.Members...) + + funcOverloads = append(funcOverloads, modules.Function{ + DataDecl: funcDataDecl, + Params: paramsMembers, + ReturnType: returns, + Locals: makeFuncLocals(rpc, nameResolver, "HttpKey", "HttpKey"), + }) + } + // Session overload + { + params, err := m.MapMessage(rpc.RequestType, api, nameResolver) + if err != nil { + return nil, err + } + sessionParam := modules.DataDecl{ + Type: "const FNakamaSession&", + Name: "Session", + Comment: "The session of the user.", + } + paramsMembers := append([]modules.DataDecl{sessionParam}, params.Members...) + + funcOverloads = append(funcOverloads, modules.Function{ + DataDecl: funcDataDecl, + Params: paramsMembers, + ReturnType: returns, + Locals: makeFuncLocals(rpc, nameResolver, "Bearer", "Session.Token"), + }) + } + + return funcOverloads, nil +} + +func (m UnrealHttpApiMapper) MapMessage(message *yacg.ProtoMessage, api yacg.Api, nameResolver modules.NameResolver) (modules.Type, error) { + if message == nil { + return modules.Type{}, nil + } + + members := make([]modules.DataDecl, 0, len(message.Fields)+len(message.MapFields)) + + message, ok := api.MessagesByName[message.Name] + if !ok { + return modules.Type{}, fmt.Errorf("type definition not found in proto schema: `%s`", message.Name) + } + for _, field := range message.Fields { + fieldTypeCtx := modules.FieldType + if field.Repeated { + fieldTypeCtx = modules.RepeatedFieldType + } + + members = append(members, modules.DataDecl{ + Name: nameResolver.Resolve(field.Name, modules.FieldName), + Type: nameResolver.Resolve(field.Type, fieldTypeCtx), + Comment: field.Comment.Message(), + }) + } + for _, field := range message.MapFields { + members = append(members, modules.DataDecl{ + Name: nameResolver.Resolve(field.Name, modules.FieldName), + Type: nameResolver.Resolve(field.Type, modules.MapType), + Comment: field.Comment.Message(), + }) + } + for _, field := range message.OneofFields { + members = append(members, modules.DataDecl{ + Name: nameResolver.Resolve(field.Name, modules.FieldName), + Type: nameResolver.Resolve(field.Type, modules.FieldType), + Comment: field.Comment.Message(), + }) + } + + return modules.Type{Members: members}, nil +} diff --git a/cmd/codegen-modular/unreal-sdk/go.mod b/cmd/codegen-modular/unreal-sdk/go.mod new file mode 100644 index 000000000..5a953c085 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/go.mod @@ -0,0 +1,5 @@ +module heroiclabs.com/yacg-modules/unrealsdk + +go 1.22.2 + +require github.com/golang-cz/textcase v1.2.1 // indirect diff --git a/cmd/codegen-modular/unreal-sdk/go.sum b/cmd/codegen-modular/unreal-sdk/go.sum new file mode 100644 index 000000000..e8584ef77 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/go.sum @@ -0,0 +1,2 @@ +github.com/golang-cz/textcase v1.2.1 h1:0xRtKo+abtJojre5ONjuMzyg9fSfiKBj5bWZ6fpTYxI= +github.com/golang-cz/textcase v1.2.1/go.mod h1:aWsQknYwxtTS2zSCrGGoRIsxmzjsHomRqLeMeVb+SKU= diff --git a/cmd/codegen-modular/unreal-sdk/main.go b/cmd/codegen-modular/unreal-sdk/main.go new file mode 100644 index 000000000..36cc84cdf --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "log" + "text/template" + "time" + + "heroiclabs.com/yacg/modules" +) + +func getFuncMap() template.FuncMap { + return template.FuncMap{ + "currentYear": func() int { + return time.Now().Year() + }, + } +} + +func main() { + mapper := UnrealHttpApiMapper{} + nameResolver := NewUnrealNameResolver() + funcMap := getFuncMap() + + module := modules.Module{ + Requires: modules.Requirements{ + Protos: []string{ + "protos/nakama-types.proto", + "protos/nakama-api.proto", + }, + }, + Produces: []modules.Production{ + { + Template: "templates/Nakama.h.tmpl", + FuncMap: funcMap, + Mapper: mapper, + NameResolver: nameResolver, + Output: "Nakama.h", + }, + { + Template: "templates/Nakama.cpp.tmpl", + FuncMap: funcMap, + Mapper: mapper, + NameResolver: nameResolver, + Output: "Nakama.cpp", + }, + }, + } + + compiled, err := module.Compile() + if err != nil { + log.Fatalf("Failed to compile module: %s", err) + } + + compiled.Generate("out") +} diff --git a/cmd/codegen-modular/unreal-sdk/name_resolver.go b/cmd/codegen-modular/unreal-sdk/name_resolver.go new file mode 100644 index 000000000..c98351d30 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/name_resolver.go @@ -0,0 +1,272 @@ +package main + +import ( + "fmt" + + "heroiclabs.com/yacg/modules" +) + +type UnrealNameResolver struct { + entries map[string]*modules.TypeEntry +} + +func NewUnrealNameResolver() *UnrealNameResolver { + stringEntry := &modules.TypeEntry{ + Param: "const FString&", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "String", + JsonArrayValue: "String", + QueryFormat: "%s", + EmptyCheck: "IsEmpty", + + FieldType: "FString", + RepeatedFieldType: "TArray", + FieldDefault: "", + + JsonGetter: "GetStringField", + CastFromJson: "", + ArrayItemExpr: "Item->AsString()", + NeedsHasCheck: true, + NeedsEmptyGuard: true, + } + + boolEntry := &modules.TypeEntry{ + Param: "bool", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Bool", + JsonArrayValue: "Boolean", + QueryFormat: "%s_bool", + EmptyCheck: "None", + + FieldType: "bool", + RepeatedFieldType: "TArray", + FieldDefault: " = false", + + JsonGetter: "GetBoolField", + CastFromJson: "", + ArrayItemExpr: "Item->AsBool()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + int32Entry := &modules.TypeEntry{ + Param: "int32", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%d", + EmptyCheck: "NonZero", + + FieldType: "int32", + RepeatedFieldType: "TArray", + FieldDefault: " = 0", + + JsonGetter: "GetIntegerField", + CastFromJson: "", + ArrayItemExpr: "static_cast(Item->AsNumber())", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + uint32Entry := *int32Entry + uint32Entry.CastFromJson = "static_cast" + uint32Entry.JsonGetter = "GetNumberField" + uint32Entry.ArrayItemExpr = "static_cast(Item->AsNumber())" + + int64Entry := &modules.TypeEntry{ + Param: "int64", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%lld", + EmptyCheck: "NonZero", + + FieldType: "int64", + RepeatedFieldType: "TArray", + FieldDefault: " = 0", + + JsonGetter: "GetNumberField", + CastFromJson: "static_cast", + ArrayItemExpr: "static_cast(Item->AsNumber())", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + uint64Entry := *int64Entry + uint64Entry.CastFromJson = "static_cast" + uint64Entry.ArrayItemExpr = "static_cast(Item->AsNumber())" + + floatEntry := &modules.TypeEntry{ + Param: "float", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%f", + EmptyCheck: "NonZero", + + FieldType: "float", + RepeatedFieldType: "TArray", + FieldDefault: " = 0.0f", + + JsonGetter: "GetNumberField", + CastFromJson: "", + ArrayItemExpr: "Item->AsNumber()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + doubleEntry := &modules.TypeEntry{ + Param: "double", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%f", + EmptyCheck: "NonZero", + + FieldType: "double", + RepeatedFieldType: "TArray", + FieldDefault: " = 0.0", + + JsonGetter: "GetNumberField", + CastFromJson: "", + ArrayItemExpr: "Item->AsNumber()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + bytesEntry := &modules.TypeEntry{ + Param: "const TArray&", + RepeatedParam: "const TArray>&", + MapParam: "const TMap>&", + MapType: "TMap>", + + JsonMethod: "String", + JsonArrayValue: "String", + QueryFormat: "%s", + EmptyCheck: "NumEmpty", + + FieldType: "TArray", + RepeatedFieldType: "TArray>", + FieldDefault: "", + + JsonGetter: "GetStringField", + CastFromJson: "", + ArrayItemExpr: "Item->AsString()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + return &UnrealNameResolver{ + entries: map[string]*modules.TypeEntry{ + "string": stringEntry, + "google.protobuf.StringValue": stringEntry, + "google.protobuf.Timestamp": stringEntry, + + "bool": boolEntry, + "google.protobuf.BoolValue": boolEntry, + + "int32": int32Entry, + "google.protobuf.Int32Value": int32Entry, + "uint32": &uint32Entry, + "google.protobuf.UInt32Value": &uint32Entry, + + "int64": int64Entry, + "google.protobuf.Int64Value": int64Entry, + "uint64": &uint64Entry, + "google.protobuf.UInt64Value": &uint64Entry, + + "float": floatEntry, + "google.protobuf.FloatValue": floatEntry, + "double": doubleEntry, + "google.protobuf.DoubleValue": doubleEntry, + + "bytes": bytesEntry, + "google.protobuf.BytesValue": bytesEntry, + }, + } +} + +const ( + Extended modules.NameResolveContext = iota + modules.SENTINEL_STD_RESOLVE_CTX + FuncReturnTypeName + SuccessLambdaType + + QueryValueSetter + + MaybeToJson +) + +func (r *UnrealNameResolver) Resolve(input string, ctx modules.NameResolveContext) string { + return fmt.Sprintf("RESOLVED_%s", input) +} + +/* + +Param: "const FString&", +RepeatedParam: "const TArray&", +MapParam: "const TMap&", +MapType: "TMap", + +JsonMethod: "String", +JsonArrayValue: "String", +QueryFormat: "%s", +EmptyCheck: "IsEmpty", + +FieldType: "FString", +RepeatedFieldType: "TArray", +FieldDefault: "", + +JsonGetter: "GetStringField", +CastFromJson: "", +ArrayItemExpr: "Item->AsString()", +NeedsHasCheck: true, +NeedsEmptyGuard: true, + +func (m *UnrealTypeMap) _Resolve(protoType string) *modules.TypeEntry { + e, ok := m.entries[protoType] + if ok { + return e + } + + unrealType := textcase.PascalCase(protoType) + return &modules.TypeEntry{ + Param: fmt.Sprintf("const %s&", unrealType), + RepeatedParam: fmt.Sprintf("const TArray<%s>&", unrealType), + MapParam: fmt.Sprintf("const TMap&", unrealType), + MapType: fmt.Sprintf("TMap", unrealType), + + JsonMethod: "String", + JsonArrayValue: "String", + QueryFormat: "%s", + EmptyCheck: "NumEmpty", + + FieldType: unrealType, + RepeatedFieldType: fmt.Sprintf("TArray<%s>", unrealType), + FieldDefault: "", + + JsonGetter: "GetStringField", + CastFromJson: "", + ArrayItemExpr: "Item->AsString()", + NeedsHasCheck: false, + NeedsEmptyGuard: true, + } +} +*/ diff --git a/cmd/codegen-modular/unreal-sdk/protos/nakama-api.proto b/cmd/codegen-modular/unreal-sdk/protos/nakama-api.proto new file mode 100644 index 000000000..d130c2101 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/protos/nakama-api.proto @@ -0,0 +1,760 @@ +// Copyright 2018 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The Nakama server RPC protocol for games and apps. + */ +syntax = "proto3"; + +package nakama.api; + +import "api/api.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/heroiclabs/nakama/v3/apigrpc"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaApiGrpc"; +option java_package = "com.heroiclabs.nakama.api"; + +option csharp_namespace = "Nakama.Protobuf"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "Nakama API v2"; + version: "2.0"; + contact: { + name: "The Nakama Authors & Contributors"; + url: "https://github.com/heroiclabs/nakama"; + email: "hello@heroiclabs.com"; + }; + }; + host: "127.0.0.1:7350"; + external_docs: { + url: "https://heroiclabs.com/docs"; + description: "Nakama server documentation"; + } + schemes: HTTP; + consumes: "application/json"; + produces: "application/json"; + security_definitions: { + security: { + key: "BasicAuth"; + value: { + type: TYPE_BASIC; + } + } + security: { + key: "BearerJwt" + value: { + type: TYPE_API_KEY + in: IN_HEADER + name: "Authorization" + } + } + security: { + key: "HttpKeyAuth"; + value: { + type: TYPE_API_KEY; + in: IN_HEADER; + name: "http_key"; + } + } + } + // Default security definition. + security: { + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } +}; + +/** + * The Nakama RPC protocol service built with GRPC. + */ +service Nakama { + // Add friends by ID or username to a user's account. + rpc AddFriends (api.AddFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/friend"; + } + + // Add users to a group. + rpc AddGroupUsers (api.AddGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/add"; + } + + // Refresh a user's session using a refresh token retrieved from a previous authentication request. + rpc SessionRefresh (api.SessionRefreshRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/session/refresh", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + rpc SessionLogout (api.SessionLogoutRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/session/logout", + body: "*" + }; + } + + // Authenticate a user with an Apple ID against the server. + rpc AuthenticateApple (api.AuthenticateAppleRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/apple", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a custom id against the server. + rpc AuthenticateCustom (api.AuthenticateCustomRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/custom", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a device id against the server. + rpc AuthenticateDevice (api.AuthenticateDeviceRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/device", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with an email+password against the server. + rpc AuthenticateEmail (api.AuthenticateEmailRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/email", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a Facebook OAuth token against the server. + rpc AuthenticateFacebook (api.AuthenticateFacebookRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/facebook", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with a Facebook Instant Game token against the server. + rpc AuthenticateFacebookInstantGame (api.AuthenticateFacebookInstantGameRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/facebookinstantgame", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Apple's GameCenter against the server. + rpc AuthenticateGameCenter (api.AuthenticateGameCenterRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/gamecenter", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Google against the server. + rpc AuthenticateGoogle (api.AuthenticateGoogleRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/google", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Authenticate a user with Steam against the server. + rpc AuthenticateSteam (api.AuthenticateSteamRequest) returns (api.Session) { + option (google.api.http) = { + post: "/v2/account/authenticate/steam", + body: "account" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Ban a set of users from a group. + rpc BanGroupUsers (api.BanGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/ban"; + } + + // Block one or more users by ID or username. + rpc BlockFriends (api.BlockFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/block" + }; + } + + // Create a new group with the current user as the owner. + rpc CreateGroup (api.CreateGroupRequest) returns (api.Group) { + option (google.api.http) = { + post: "/v2/group", + body: "*" + }; + } + + // Delete the current user's account. + rpc DeleteAccount (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/account"; + } + + // Delete one or more users by ID or username. + rpc DeleteFriends (api.DeleteFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/friend"; + } + + // Delete a group by ID. + rpc DeleteGroup (api.DeleteGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/group/{group_id}"; + } + + // Delete a leaderboard record. + rpc DeleteLeaderboardRecord (api.DeleteLeaderboardRecordRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/leaderboard/{leaderboard_id}"; + } + + // Delete one or more notifications for the current user. + rpc DeleteNotifications (api.DeleteNotificationsRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/notification"; + } + + // Delete a tournament record. + rpc DeleteTournamentRecord (api.DeleteTournamentRecordRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/tournament/{tournament_id}"; + } + + // Delete one or more objects by ID or username. + rpc DeleteStorageObjects (api.DeleteStorageObjectsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/storage/delete", + body: "*" + }; + } + + // Submit an event for processing in the server's registered runtime custom events handler. + rpc Event (api.Event) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/event", + body: "*" + }; + } + + // Fetch the current user's account. + rpc GetAccount (google.protobuf.Empty) returns (api.Account) { + option (google.api.http).get = "/v2/account"; + } + + // Fetch zero or more users by ID and/or username. + rpc GetUsers (api.GetUsersRequest) returns (api.Users) { + option (google.api.http).get = "/v2/user"; + } + + // Get subscription by product id. + rpc GetSubscription (api.GetSubscriptionRequest) returns (api.ValidatedSubscription) { + option (google.api.http).get = "/v2/iap/subscription/{product_id}"; + } + + // Get matchmaker stats. + rpc GetMatchmakerStats (google.protobuf.Empty) returns (api.MatchmakerStats) { + option (google.api.http).get = "/v2/matchmaker/stats"; + } + + // A healthcheck which load balancers can use to check the service. + rpc Healthcheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/healthcheck"; + } + + // Import Facebook friends and add them to a user's account. + rpc ImportFacebookFriends (api.ImportFacebookFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/facebook", + body: "account" + }; + } + + // Import Steam friends and add them to a user's account. + rpc ImportSteamFriends (api.ImportSteamFriendsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/friend/steam", + body: "account" + }; + } + + // Immediately join an open group, or request to join a closed one. + rpc JoinGroup (api.JoinGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/join"; + } + + // Attempt to join an open and running tournament. + rpc JoinTournament (api.JoinTournamentRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/tournament/{tournament_id}/join"; + } + + // Kick a set of users from a group. + rpc KickGroupUsers (api.KickGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/kick"; + } + + // Leave a group the user is a member of. + rpc LeaveGroup (api.LeaveGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/leave"; + } + + // Add an Apple ID to the social profiles on the current user's account. + rpc LinkApple (api.AccountApple) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/apple", + body: "*" + }; + } + + // Add a custom ID to the social profiles on the current user's account. + rpc LinkCustom (api.AccountCustom) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/custom", + body: "*" + }; + } + + // Add a device ID to the social profiles on the current user's account. + rpc LinkDevice (api.AccountDevice) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/device", + body: "*" + }; + } + + // Add an email+password to the social profiles on the current user's account. + rpc LinkEmail (api.AccountEmail) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/email", + body: "*" + }; + } + + // Add Facebook to the social profiles on the current user's account. + rpc LinkFacebook (api.LinkFacebookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/facebook", + body: "account" + }; + } + + // Add Facebook Instant Game to the social profiles on the current user's account. + rpc LinkFacebookInstantGame (api.AccountFacebookInstantGame) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/facebookinstantgame", + body: "*" + }; + } + + // Add Apple's GameCenter to the social profiles on the current user's account. + rpc LinkGameCenter (api.AccountGameCenter) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/gamecenter", + body: "*" + }; + } + + // Add Google to the social profiles on the current user's account. + rpc LinkGoogle (api.AccountGoogle) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/google", + body: "*" + }; + } + + // Add Steam to the social profiles on the current user's account. + rpc LinkSteam (api.LinkSteamRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/link/steam", + body: "*" + }; + } + + // List a channel's message history. + rpc ListChannelMessages (api.ListChannelMessagesRequest) returns (api.ChannelMessageList) { + option (google.api.http).get = "/v2/channel/{channel_id}"; + } + + // List all friends for the current user. + rpc ListFriends (api.ListFriendsRequest) returns (api.FriendList) { + option (google.api.http).get = "/v2/friend"; + } + + // List friends of friends for the current user. + rpc ListFriendsOfFriends(api.ListFriendsOfFriendsRequest) returns (api.FriendsOfFriendsList) { + option (google.api.http).get = "/v2/friend/friends"; + } + + // List groups based on given filters. + rpc ListGroups (api.ListGroupsRequest) returns (api.GroupList) { + option (google.api.http).get = "/v2/group"; + } + + // List all users that are part of a group. + rpc ListGroupUsers (api.ListGroupUsersRequest) returns (api.GroupUserList) { + option (google.api.http).get = "/v2/group/{group_id}/user"; + } + + // List leaderboard records. + rpc ListLeaderboardRecords (api.ListLeaderboardRecordsRequest) returns (api.LeaderboardRecordList) { + option (google.api.http).get = "/v2/leaderboard/{leaderboard_id}"; + } + + // List leaderboard records around the target ownerId. + rpc ListLeaderboardRecordsAroundOwner (api.ListLeaderboardRecordsAroundOwnerRequest) returns (api.LeaderboardRecordList) { + option (google.api.http).get = "/v2/leaderboard/{leaderboard_id}/owner/{owner_id}"; + } + + // List running matches and optionally filter by matching criteria. + rpc ListMatches (api.ListMatchesRequest) returns (api.MatchList) { + option (google.api.http).get = "/v2/match"; + } + + // List parties and optionally filter by matching criteria. + rpc ListParties (api.ListPartiesRequest) returns (api.PartyList) { + option (google.api.http).get = "/v2/party"; + } + + // Fetch list of notifications. + rpc ListNotifications (api.ListNotificationsRequest) returns (api.NotificationList) { + option (google.api.http).get = "/v2/notification"; + } + + // List publicly readable storage objects in a given collection. + rpc ListStorageObjects (api.ListStorageObjectsRequest) returns (api.StorageObjectList) { + option (google.api.http) = { + get: "/v2/storage/{collection}", + additional_bindings { + get: "/v2/storage/{collection}/{user_id}" + } + }; + } + + // List user's subscriptions. + rpc ListSubscriptions (api.ListSubscriptionsRequest) returns (api.SubscriptionList) { + option (google.api.http) = { + post: "/v2/iap/subscription", + body: "*" + }; + } + + // List current or upcoming tournaments. + rpc ListTournaments (api.ListTournamentsRequest) returns (api.TournamentList) { + option (google.api.http).get = "/v2/tournament"; + } + + // List tournament records. + rpc ListTournamentRecords (api.ListTournamentRecordsRequest) returns (api.TournamentRecordList) { + option (google.api.http).get = "/v2/tournament/{tournament_id}"; + } + + // List tournament records for a given owner. + rpc ListTournamentRecordsAroundOwner (api.ListTournamentRecordsAroundOwnerRequest) returns (api.TournamentRecordList) { + option (google.api.http).get = "/v2/tournament/{tournament_id}/owner/{owner_id}"; + } + + // List groups the current user belongs to. + rpc ListUserGroups (api.ListUserGroupsRequest) returns (api.UserGroupList) { + option (google.api.http).get = "/v2/user/{user_id}/group"; + } + + // Promote a set of users in a group to the next role up. + rpc PromoteGroupUsers (api.PromoteGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/promote"; + } + + // Demote a set of users in a group to the next role down. + rpc DemoteGroupUsers (api.DemoteGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http).post = "/v2/group/{group_id}/demote"; + } + + // Get storage objects. + rpc ReadStorageObjects (api.ReadStorageObjectsRequest) returns (api.StorageObjects) { + option (google.api.http) = { + post: "/v2/storage", + body: "*" + }; + } + + // Execute a Lua function on the server. + rpc RpcFunc (api.Rpc) returns (api.Rpc) { + option (google.api.http) = { + post: "/v2/rpc/{id}", + body: "payload", + additional_bindings { + get: "/v2/rpc/{id}" + } + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "HttpKeyAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // Remove the Apple ID from the social profiles on the current user's account. + rpc UnlinkApple (api.AccountApple) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/apple", + body: "*" + }; + } + + // Remove the custom ID from the social profiles on the current user's account. + rpc UnlinkCustom (api.AccountCustom) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/custom", + body: "*" + }; + } + + // Remove the device ID from the social profiles on the current user's account. + rpc UnlinkDevice (api.AccountDevice) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/device", + body: "*" + }; + } + + // Remove the email+password from the social profiles on the current user's account. + rpc UnlinkEmail (api.AccountEmail) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/email", + body: "*" + }; + } + + // Remove Facebook from the social profiles on the current user's account. + rpc UnlinkFacebook (api.AccountFacebook) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/facebook", + body: "*" + }; + } + + // Remove Facebook Instant Game profile from the social profiles on the current user's account. + rpc UnlinkFacebookInstantGame (api.AccountFacebookInstantGame) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/facebookinstantgame", + body: "*" + }; + } + + // Remove Apple's GameCenter from the social profiles on the current user's account. + rpc UnlinkGameCenter (api.AccountGameCenter) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/gamecenter", + body: "*" + }; + } + + // Remove Google from the social profiles on the current user's account. + rpc UnlinkGoogle (api.AccountGoogle) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/google", + body: "*" + }; + } + + // Remove Steam from the social profiles on the current user's account. + rpc UnlinkSteam (api.AccountSteam) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/account/unlink/steam", + body: "*" + }; + } + + // Update fields in the current user's account. + rpc UpdateAccount (api.UpdateAccountRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/account", + body: "*" + }; + } + + // Update fields in a given group. + rpc UpdateGroup (api.UpdateGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/group/{group_id}", + body: "*" + }; + } + + // Validate Apple IAP Receipt + rpc ValidatePurchaseApple (api.ValidatePurchaseAppleRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/apple", + body: "*" + }; + } + + // Validate Apple Subscription Receipt + rpc ValidateSubscriptionApple (api.ValidateSubscriptionAppleRequest) returns (api.ValidateSubscriptionResponse) { + option (google.api.http) = { + post: "/v2/iap/subscription/apple", + body: "*" + }; + } + + // Validate Google IAP Receipt + rpc ValidatePurchaseGoogle (api.ValidatePurchaseGoogleRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/google", + body: "*" + }; + } + + // Validate Google Subscription Receipt + rpc ValidateSubscriptionGoogle (api.ValidateSubscriptionGoogleRequest) returns (api.ValidateSubscriptionResponse) { + option (google.api.http) = { + post: "/v2/iap/subscription/google", + body: "*" + }; + } + + // Validate Huawei IAP Receipt + rpc ValidatePurchaseHuawei (api.ValidatePurchaseHuaweiRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/huawei", + body: "*" + }; + } + + // Validate FB Instant IAP Receipt + rpc ValidatePurchaseFacebookInstant (api.ValidatePurchaseFacebookInstantRequest) returns (api.ValidatePurchaseResponse) { + option (google.api.http) = { + post: "/v2/iap/purchase/facebookinstant", + body: "*" + }; + } + + // Write a record to a leaderboard. + rpc WriteLeaderboardRecord (api.WriteLeaderboardRecordRequest) returns (api.LeaderboardRecord) { + option (google.api.http) = { + post: "/v2/leaderboard/{leaderboard_id}", + body: "record" + }; + } + + // Write objects into the storage engine. + rpc WriteStorageObjects (api.WriteStorageObjectsRequest) returns (api.StorageObjectAcks) { + option (google.api.http) = { + put: "/v2/storage", + body: "*" + }; + } + + // Write a record to a tournament. + rpc WriteTournamentRecord (api.WriteTournamentRecordRequest) returns (api.LeaderboardRecord) { + option (google.api.http) = { + put: "/v2/tournament/{tournament_id}", + body: "record", + additional_bindings { + post: "/v2/tournament/{tournament_id}", + body: "record", + } + }; + } +} diff --git a/cmd/codegen-modular/unreal-sdk/protos/nakama-types.proto b/cmd/codegen-modular/unreal-sdk/protos/nakama-types.proto new file mode 100644 index 000000000..23467996e --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/protos/nakama-types.proto @@ -0,0 +1,1459 @@ +// Copyright 2019 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The Nakama server RPC protocol for games and apps. + */ +syntax = "proto3"; + +package nakama.api; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "github.com/heroiclabs/nakama-common/api"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaApi"; +option java_package = "com.heroiclabs.nakama.api"; + +option csharp_namespace = "Nakama.Protobuf"; + + +// A user in the server. +message User { + // The id of the user's account. + string id = 1; + // The username of the user's account. + string username = 2; + // The display name of the user. + string display_name = 3; + // A URL for an avatar image. + string avatar_url = 4; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 5; + // The location set by the user. + string location = 6; + // The timezone set by the user. + string timezone = 7; + // Additional information stored as a JSON object. + string metadata = 8; + // The Facebook id in the user's account. + string facebook_id = 9; + // The Google id in the user's account. + string google_id = 10; + // The Apple Game Center in of the user's account. + string gamecenter_id = 11; + // The Steam id in the user's account. + string steam_id = 12; + // Indicates whether the user is currently online. + bool online = 13; + // Number of related edges to this user. + int32 edge_count = 14; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was created. + google.protobuf.Timestamp create_time = 15; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user was last updated. + google.protobuf.Timestamp update_time = 16; + // The Facebook Instant Game ID in the user's account. + string facebook_instant_game_id = 17; + // The Apple Sign In ID in the user's account. + string apple_id = 18; +} + +// Send a device to the server. Used with authenticate/link/unlink and user. +message AccountDevice { + // A device identifier. Should be obtained by a platform-specific device API. + string id = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// A user with additional account details. Always the current user. +message Account { + // The user object. + User user = 1; + // The user's wallet data. + string wallet = 2; + // The email address of the user. + string email = 3; + // The devices which belong to the user's account. + repeated AccountDevice devices = 4; + // The custom id in the user's account. + string custom_id = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's email was verified. + google.protobuf.Timestamp verify_time = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the user's account was disabled/banned. + google.protobuf.Timestamp disable_time = 7; +} + +// Obtain a new authentication token using a refresh token. +message AccountRefresh { + // Refresh token. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Apple Sign In token to the server. Used with authenticate/link/unlink. +message AccountApple { + // The ID token received from Apple to validate. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a custom ID to the server. Used with authenticate/link/unlink. +message AccountCustom { + // A custom identifier. + string id = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + + +// Send an email with password to the server. Used with authenticate/link/unlink. +message AccountEmail { + // A valid RFC-5322 email address. + string email = 1; + // A password for the user account. + string password = 2; // Ignored with unlink operations. + // Extra information that will be bundled in the session token. + map vars = 3; +} + +// Send a Facebook token to the server. Used with authenticate/link/unlink. +message AccountFacebook { + // The OAuth token received from Facebook to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Facebook Instant Game token to the server. Used with authenticate/link/unlink. +message AccountFacebookInstantGame { + // The OAuth token received from a Facebook Instant Game that may be decoded with the Application Secret (must be available with the nakama configuration) + string signed_player_info = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send Apple's Game Center account credentials to the server. Used with authenticate/link/unlink. +message AccountGameCenter { + // https://developer.apple.com/documentation/gamekit/gklocalplayer/1515407-generateidentityverificationsign + + // Player ID (generated by GameCenter). + string player_id = 1; + // Bundle ID (generated by GameCenter). + string bundle_id = 2; + // Time since UNIX epoch when the signature was created. + int64 timestamp_seconds = 3; + // A random "NSString" used to compute the hash and keep it randomized. + string salt = 4; + // The verification signature data generated. + string signature = 5; + // The URL for the public encryption key. + string public_key_url = 6; + // Extra information that will be bundled in the session token. + map vars = 7; +} + +// Send a Google token to the server. Used with authenticate/link/unlink. +message AccountGoogle { + // The OAuth token received from Google to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Send a Steam token to the server. Used with authenticate/link/unlink. +message AccountSteam { + // The account token received from Steam to access their profile API. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Add one or more friends to the current user. +message AddFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; + // Optional metadata to add to friends. + string metadata = 3; +} + +// Add users to a group. +message AddGroupUsersRequest { + // The group to add users to. + string group_id = 1; + // The users to add. + repeated string user_ids = 2; +} + +// Authenticate against the server with a refresh token. +message SessionRefreshRequest { + // Refresh token. + string token = 1; + // Extra information that will be bundled in the session token. + map vars = 2; +} + +// Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. +message SessionLogoutRequest { + // Session token to log out. + string token = 1; + // Refresh token to invalidate. + string refresh_token = 2; +} + +// Authenticate against the server with Apple Sign In. +message AuthenticateAppleRequest { + // The Apple account details. + AccountApple account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with a custom ID. +message AuthenticateCustomRequest { + // The custom account details. + AccountCustom account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with a device ID. +message AuthenticateDeviceRequest { + // The device account details. + AccountDevice account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with email+password. +message AuthenticateEmailRequest { + // The email account details. + AccountEmail account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Facebook. +message AuthenticateFacebookRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; + // Import Facebook friends for the user. + google.protobuf.BoolValue sync = 4; +} + +// Authenticate against the server with Facebook Instant Game token. +message AuthenticateFacebookInstantGameRequest { + // The Facebook Instant Game account details. + AccountFacebookInstantGame account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Apple's Game Center. +message AuthenticateGameCenterRequest { + // The Game Center account details. + AccountGameCenter account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Google. +message AuthenticateGoogleRequest { + // The Google account details. + AccountGoogle account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; +} + +// Authenticate against the server with Steam. +message AuthenticateSteamRequest { + // The Steam account details. + AccountSteam account = 1; + // Register the account if the user does not already exist. + google.protobuf.BoolValue create = 2; + // Set the username on the account at register. Must be unique. + string username = 3; + // Import Steam friends for the user. + google.protobuf.BoolValue sync = 4; +} + +// Ban users from a group. +message BanGroupUsersRequest { + // The group to ban users from. + string group_id = 1; + // The users to ban. + repeated string user_ids = 2; +} + +// Block one or more friends for the current user. +message BlockFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; +} + +// A message sent on a channel. +message ChannelMessage { + // The channel this message belongs to. + string channel_id = 1; + // The unique ID of this message. + string message_id = 2; + // The code representing a message type or category. + google.protobuf.Int32Value code = 3; + // Message sender, usually a user ID. + string sender_id = 4; + // The username of the message sender, if any. + string username = 5; + // The content payload. + string content = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + google.protobuf.Timestamp create_time = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + google.protobuf.Timestamp update_time = 8; + // True if the message was persisted to the channel's history, false otherwise. + google.protobuf.BoolValue persistent = 9; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 10; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 11; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 12; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 13; +} + +// A list of channel messages, usually a result of a list operation. +message ChannelMessageList { + // A list of messages. + repeated ChannelMessage messages = 1; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; + // Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. + string cacheable_cursor = 4; +} + +// Create a group with the current user as owner. +message CreateGroupRequest { + // A unique name for the group. + string name = 1; + // A description for the group. + string description = 2; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 3; + // A URL for an avatar image. + string avatar_url = 4; + // Mark a group as open or not where only admins can accept members. + bool open = 5; + // Maximum number of group members. + int32 max_count = 6; +} + +// Delete one or more friends for the current user. +message DeleteFriendsRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; +} + +// Delete a group the user has access to. +message DeleteGroupRequest { + // The id of a group. + string group_id = 1; +} + +// Delete a leaderboard record. +message DeleteLeaderboardRecordRequest { + // The leaderboard ID to delete from. + string leaderboard_id = 1; +} + +// Delete one or more notifications for the current user. +message DeleteNotificationsRequest { + // The id of notifications. + repeated string ids = 1; +} + +// Delete a leaderboard record. +message DeleteTournamentRecordRequest { + // The tournament ID to delete from. + string tournament_id = 1; +} + +// Storage objects to delete. +message DeleteStorageObjectId { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The version hash of the object. + string version = 3; +} + +// Batch delete storage objects. +message DeleteStorageObjectsRequest { + // Batch of storage objects. + repeated DeleteStorageObjectId object_ids = 1; +} + +// Represents an event to be passed through the server to registered event handlers. +message Event { + // An event name, type, category, or identifier. + string name = 1; + // Arbitrary event property values. + map properties = 2; + // The time when the event was triggered. + google.protobuf.Timestamp timestamp = 3; + // True if the event came directly from a client call, false otherwise. + bool external = 4; +} + +// A friend of a user. +message Friend { + // The friendship status. + enum State { + // The user is a friend of the current user. + FRIEND = 0; + // The current user has sent an invite to the user. + INVITE_SENT = 1; + // The current user has received an invite from this user. + INVITE_RECEIVED = 2; + // The current user has blocked this user. + BLOCKED = 3; + } + + // The user object. + User user = 1; + // The friend status. + google.protobuf.Int32Value state = 2; // one of "Friend.State". + // Time of the latest relationship update. + google.protobuf.Timestamp update_time = 3; + // Metadata. + string metadata = 4; +} + +// A collection of zero or more friends of the user. +message FriendList { + // The Friend objects. + repeated Friend friends = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// A friend of a friend. +message FriendsOfFriendsList_FriendOfFriend { + // The user who referred its friend. + string referrer = 1; + // User. + User user = 2; +} + +// A List of friends of friends +message FriendsOfFriendsList { + // User friends of friends. + repeated FriendsOfFriendsList_FriendOfFriend friends_of_friends = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// Fetch a batch of zero or more users from the server. +message GetUsersRequest { + // The account id of a user. + repeated string ids = 1; + // The account username of a user. + repeated string usernames = 2; + // The Facebook ID of a user. + repeated string facebook_ids = 3; +} + +// Fetch a subscription by product id. +message GetSubscriptionRequest { + // Product id of the subscription + string product_id = 1; +} + +// A group in the server. +message Group { + // The id of a group. + string id = 1; + // The id of the user who created the group. + string creator_id = 2; + // The unique name of the group. + string name = 3; + // A description for the group. + string description = 4; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 5; + // Additional information stored as a JSON object. + string metadata = 6; + // A URL for an avatar image. + string avatar_url = 7; + // Anyone can join open groups, otherwise only admins can accept members. + google.protobuf.BoolValue open = 8; + // The current count of all members in the group. + int32 edge_count = 9; + // The maximum number of members allowed. + int32 max_count = 10; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was created. + google.protobuf.Timestamp create_time = 11; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the group was last updated. + google.protobuf.Timestamp update_time = 12; +} + +// One or more groups returned from a listing operation. +message GroupList { + // One or more groups. + repeated Group groups = 1; + // A cursor used to get the next page. + string cursor = 2; +} + +message GroupUserList_GroupUser { + // The group role status. + enum State { + // The user is a superadmin with full control of the group. + SUPERADMIN = 0; + // The user is an admin with additional privileges. + ADMIN = 1; + // The user is a regular member. + MEMBER = 2; + // The user has requested to join the group + JOIN_REQUEST = 3; + } + // User. + User user = 1; + // Their relationship to the group. + google.protobuf.Int32Value state = 2; +} + +// A list of users belonging to a group, along with their role. +message GroupUserList { + // User-role pairs for a group. + repeated GroupUserList_GroupUser group_users = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// Import Facebook friends into the current user's account. +message ImportFacebookFriendsRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Reset the current user's friends list. + google.protobuf.BoolValue reset = 2; +} + +// Import Facebook friends into the current user's account. +message ImportSteamFriendsRequest { + // The Facebook account details. + AccountSteam account = 1; + // Reset the current user's friends list. + google.protobuf.BoolValue reset = 2; +} + +// Immediately join an open group, or request to join a closed one. +message JoinGroupRequest { + // The group ID to join. The group must already exist. + string group_id = 1; +} + +// The request to join a tournament. +message JoinTournamentRequest { + // The ID of the tournament to join. The tournament must already exist. + string tournament_id = 1; +} + +// Kick a set of users from a group. +message KickGroupUsersRequest { + // The group ID to kick from. + string group_id = 1; + // The users to kick. + repeated string user_ids = 2; +} + +// A leaderboard on the server. +message Leaderboard { + // The ID of the leaderboard. + string id = 1; + // ASC(0) or DESC(1) sort mode of scores in the leaderboard. + uint32 sort_order = 2; + // BEST, SET, INCREMENT or DECREMENT operator mode of the leaderboard. + Operator operator = 3; + // The UNIX time when the leaderboard was previously reset. A computed value. + uint32 prev_reset = 4; + // The UNIX time when the leaderboard is next playable. A computed value. + uint32 next_reset = 5; + // Additional information stored as a JSON object. + string metadata = 6; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard was created. + google.protobuf.Timestamp create_time = 7; + // Whether the leaderboard was created authoritatively or not. + bool authoritative = 8; +} + +// A list of leaderboards +message LeaderboardList { + // The list of leaderboards returned. + repeated Leaderboard leaderboards = 1; + // A pagination cursor (optional). + string cursor = 2; +} + +// Represents a complete leaderboard record with all scores and associated metadata. +message LeaderboardRecord { + // The ID of the leaderboard this score belongs to. + string leaderboard_id = 1; + // The ID of the score owner, usually a user or group. + string owner_id = 2; + // The username of the score owner, if the owner is a user. + google.protobuf.StringValue username = 3; + // The score value. + int64 score = 4; + // An optional subscore value. + int64 subscore = 5; + // The number of submissions to this score record. + int32 num_score = 6; + // Metadata. + string metadata = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was created. + google.protobuf.Timestamp create_time = 8; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record was updated. + google.protobuf.Timestamp update_time = 9; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the leaderboard record expires. + google.protobuf.Timestamp expiry_time = 10; + // The rank of this record. + int64 rank = 11; + // The maximum number of score updates allowed by the owner. + uint32 max_num_score = 12; +} + +// A set of leaderboard records, may be part of a leaderboard records page or a batch of individual records. +message LeaderboardRecordList { + // A list of leaderboard records. + repeated LeaderboardRecord records = 1; + // A batched set of leaderboard records belonging to specified owners. + repeated LeaderboardRecord owner_records = 2; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 3; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 4; + // The total number of ranks available. + int64 rank_count = 5; +} + +// Leave a group. +message LeaveGroupRequest { + // The group ID to leave. + string group_id = 1; +} + +// Link Facebook to the current user's account. +message LinkFacebookRequest { + // The Facebook account details. + AccountFacebook account = 1; + // Import Facebook friends for the user. + google.protobuf.BoolValue sync = 2; +} + +// Link Steam to the current user's account. +message LinkSteamRequest { + // The Facebook account details. + AccountSteam account = 1; + // Import Steam friends for the user. + google.protobuf.BoolValue sync = 2; +} + +// List a channel's message history. +message ListChannelMessagesRequest { + // The channel ID to list from. + string channel_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // True if listing should be older messages to newer, false if reverse. + google.protobuf.BoolValue forward = 3; + // A pagination cursor, if any. + string cursor = 4; +} + +// List friends for a user. +message ListFriendsRequest { + // Max number of records to return. Between 1 and 1000. + google.protobuf.Int32Value limit = 1; + // The friend state to list. + google.protobuf.Int32Value state = 2; + // An optional next page cursor. + string cursor = 3; +} + +message ListFriendsOfFriendsRequest { + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 1; + // An optional next page cursor. + string cursor = 2; +} + +// List groups based on given filters. +message ListGroupsRequest { + // List groups that contain this value in their names. + string name = 1; + // Optional pagination cursor. + string cursor = 2; + // Max number of groups to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // Language tag filter + string lang_tag = 4; + // Number of group members + google.protobuf.Int32Value members = 5; + // Optional Open/Closed filter. + google.protobuf.BoolValue open = 6; +} + +// List all users that are part of a group. +message ListGroupUsersRequest { + // The group ID to list from. + string group_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // The group user state to list. + google.protobuf.Int32Value state = 3; + // An optional next page cursor. + string cursor = 4; +} + +// List leaerboard records from a given leaderboard around the owner. +message ListLeaderboardRecordsAroundOwnerRequest { + // The ID of the tournament to list for. + string leaderboard_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.UInt32Value limit = 2; + // The owner to retrieve records around. + string owner_id = 3; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 4; + // A next or previous page cursor. + string cursor = 5; +} + +// List leaderboard records from a given leaderboard. +message ListLeaderboardRecordsRequest { + // The ID of the leaderboard to list for. + string leaderboard_id = 1; + // One or more owners to retrieve records for. + repeated string owner_ids = 2; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // A next or previous page cursor. + string cursor = 4; + // Expiry in seconds (since epoch) to begin fetching records from. Optional. 0 means from current time. + google.protobuf.Int64Value expiry = 5; +} + +// List realtime matches. +message ListMatchesRequest { + // Limit the number of returned matches. + google.protobuf.Int32Value limit = 1; + // Authoritative or relayed matches. + google.protobuf.BoolValue authoritative = 2; + // Label filter. + google.protobuf.StringValue label = 3; + // Minimum user count. + google.protobuf.Int32Value min_size = 4; + // Maximum user count. + google.protobuf.Int32Value max_size = 5; + // Arbitrary label query. + google.protobuf.StringValue query = 6; +} + +// Get a list of unexpired notifications. +message ListNotificationsRequest { + // The number of notifications to get. Between 1 and 100. + google.protobuf.Int32Value limit = 1; + // A cursor to page through notifications. May be cached by clients to get from point in time forwards. + string cacheable_cursor = 2; // value from NotificationList.cacheable_cursor. +} + +// List publicly readable storage objects in a given collection. +message ListStorageObjectsRequest { + // ID of the user. + string user_id = 1; + // The collection which stores the object. + string collection = 2; + // The number of storage objects to list. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // The cursor to page through results from. + string cursor = 4; // value from StorageObjectList.cursor. +} + +// List user subscriptions. +message ListSubscriptionsRequest { + // Max number of results per page + google.protobuf.Int32Value limit = 1; + // Cursor to retrieve a page of records from + string cursor = 2; +} + +// List tournament records from a given tournament around the owner. +message ListTournamentRecordsAroundOwnerRequest { + // The ID of the tournament to list for. + string tournament_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.UInt32Value limit = 2; + // The owner to retrieve records around. + string owner_id = 3; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 4; + // A next or previous page cursor. + string cursor = 5; +} + +// List tournament records from a given tournament. +message ListTournamentRecordsRequest { + // The ID of the tournament to list for. + string tournament_id = 1; + // One or more owners to retrieve records for. + repeated string owner_ids = 2; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 3; + // A next or previous page cursor. + string cursor = 4; + // Expiry in seconds (since epoch) to begin fetching records from. + google.protobuf.Int64Value expiry = 5; +} + +// List active/upcoming tournaments based on given filters. +message ListTournamentsRequest { + // The start of the categories to include. Defaults to 0. + google.protobuf.UInt32Value category_start = 1; + // The end of the categories to include. Defaults to 128. + google.protobuf.UInt32Value category_end = 2; + // The start time for tournaments. Defaults to epoch. + google.protobuf.UInt32Value start_time = 3; + // The end time for tournaments. Defaults to +1 year from current Unix time. + google.protobuf.UInt32Value end_time = 4; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 6; + // A next page cursor for listings (optional). + string cursor = 8; +} + +// List the groups a user is part of, and their relationship to each. +message ListUserGroupsRequest { + // ID of the user. + string user_id = 1; + // Max number of records to return. Between 1 and 100. + google.protobuf.Int32Value limit = 2; + // The user group state to list. + google.protobuf.Int32Value state = 3; + // An optional next page cursor. + string cursor = 4; +} + +// Represents a realtime match. +message Match { + // The ID of the match, can be used to join. + string match_id = 1; + // True if it's an server-managed authoritative match, false otherwise. + bool authoritative = 2; + // Match label, if any. + google.protobuf.StringValue label = 3; + // Current number of users in the match. + int32 size = 4; + // Tick Rate + int32 tick_rate = 5; + // Handler name + string handler_name = 6; +} + +// A list of realtime matches. +message MatchList { + // A number of matches corresponding to a list operation. + repeated Match matches = 1; +} + +// Matchmaker ticket completion stats +message MatchmakerCompletionStats { + google.protobuf.Timestamp create_time = 1; + google.protobuf.Timestamp complete_time = 2; +} + +// Matchmaker stats +message MatchmakerStats { + int32 ticket_count = 1; + google.protobuf.Timestamp oldest_ticket_create_time = 2; + repeated MatchmakerCompletionStats completions = 3; +} + +// A notification in the server. +message Notification { + // ID of the Notification. + string id = 1; + // Subject of the notification. + string subject = 2; + // Content of the notification in JSON. + string content = 3; + // Category code for this notification. + int32 code = 4; + // ID of the sender, if a user. Otherwise 'null'. + string sender_id = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the notification was created. + google.protobuf.Timestamp create_time = 6; + // True if this notification was persisted to the database. + bool persistent = 7; +} + +// A collection of zero or more notifications. +message NotificationList { + // Collection of notifications. + repeated Notification notifications = 1; + // Use this cursor to paginate notifications. Cache this to catch up to new notifications. + string cacheable_cursor = 2; +} + +// Promote a set of users in a group to the next role up. +message PromoteGroupUsersRequest { + // The group ID to promote in. + string group_id = 1; + // The users to promote. + repeated string user_ids = 2; +} + +// Demote a set of users in a group to the next role down. +message DemoteGroupUsersRequest { + // The group ID to demote in. + string group_id = 1; + // The users to demote. + repeated string user_ids = 2; +} + +// Storage objects to get. +message ReadStorageObjectId { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The user owner of the object. + string user_id = 3; +} + +// Batch get storage objects. +message ReadStorageObjectsRequest { + // Batch of storage objects. + repeated ReadStorageObjectId object_ids = 1; +} + +// Execute an Lua function on the server. +message Rpc { + // The identifier of the function. + string id = 1; + // The payload of the function which must be a JSON object. + string payload = 2; + // The authentication key used when executed as a non-client HTTP request. + string http_key = 3; +} + +// A user's session used to authenticate messages. +message Session { + // True if the corresponding account was just created, false otherwise. + bool created = 1; + // Authentication credentials. + string token = 2; + // Refresh token that can be used for session token renewal. + string refresh_token = 3; +} + +// An object within the storage engine. +message StorageObject { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The user owner of the object. + string user_id = 3; + // The value of the object. + string value = 4; + // The version hash of the object. + string version = 5; + // The read access permissions for the object. + int32 permission_read = 6; + // The write access permissions for the object. + int32 permission_write = 7; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. + google.protobuf.Timestamp create_time = 8; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. + google.protobuf.Timestamp update_time = 9; +} + +// A storage acknowledgement. +message StorageObjectAck { + // The collection which stores the object. + string collection = 1; + // The key of the object within the collection. + string key = 2; + // The version hash of the object. + string version = 3; + // The owner of the object. + string user_id = 4; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was created. + google.protobuf.Timestamp create_time = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the object was last updated. + google.protobuf.Timestamp update_time = 6; +} + +// Batch of acknowledgements for the storage object write. +message StorageObjectAcks { + // Batch of storage write acknowledgements. + repeated StorageObjectAck acks = 1; +} + +// Batch of storage objects. +message StorageObjects { + // The batch of storage objects. + repeated StorageObject objects = 1; +} + +// List of storage objects. +message StorageObjectList { + // The list of storage objects. + repeated StorageObject objects = 1; + // The cursor for the next page of results, if any. + string cursor = 2; +} + +// A tournament on the server. +message Tournament { + // The ID of the tournament. + string id = 1; + // The title for the tournament. + string title = 2; + // The description of the tournament. May be blank. + string description = 3; + // The category of the tournament. e.g. "vip" could be category 1. + uint32 category = 4; + // ASC (0) or DESC (1) sort mode of scores in the tournament. + uint32 sort_order = 5; + // The current number of players in the tournament. + uint32 size = 6; + // The maximum number of players for the tournament. + uint32 max_size = 7; + // The maximum score updates allowed per player for the current tournament. + uint32 max_num_score = 8; + // True if the tournament is active and can enter. A computed value. + bool can_enter = 9; + // The UNIX time when the tournament stops being active until next reset. A computed value. + uint32 end_active = 10; + // The UNIX time when the tournament is next playable. A computed value. + uint32 next_reset = 11; + // Additional information stored as a JSON object. + string metadata = 12; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament was created. + google.protobuf.Timestamp create_time = 13; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will start. + google.protobuf.Timestamp start_time = 14; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the tournament will be stopped. + google.protobuf.Timestamp end_time = 15; + // Duration of the tournament in seconds. + uint32 duration = 16; + // The UNIX time when the tournament start being active. A computed value. + uint32 start_active = 17; + // The UNIX time when the tournament was last reset. A computed value. + uint32 prev_reset = 18; + // Operator. + Operator operator = 19; + // Whether the leaderboard was created authoritatively or not. + bool authoritative = 20; + // Whether the user must join the tournament before being able to submit scores. + bool join_required = 21; +} + +// A list of tournaments. +message TournamentList { + // The list of tournaments returned. + repeated Tournament tournaments = 1; + // A pagination cursor (optional). + string cursor = 2; +} + +// A set of tournament records which may be part of a tournament records page or a batch of individual records. +message TournamentRecordList { + // A list of tournament records. + repeated LeaderboardRecord records = 1; + // A batched set of tournament records belonging to specified owners. + repeated LeaderboardRecord owner_records = 2; + // The cursor to send when retireving the next page (optional). + string next_cursor = 3; + // The cursor to send when retrieving the previous page (optional). + string prev_cursor = 4; + // The total number of ranks available. + int64 rank_count = 5; +} + +// Update a user's account details. +message UpdateAccountRequest { + // The username of the user's account. + google.protobuf.StringValue username = 1; + // The display name of the user. + google.protobuf.StringValue display_name = 2; + // A URL for an avatar image. + google.protobuf.StringValue avatar_url = 3; + // The language expected to be a tag which follows the BCP-47 spec. + google.protobuf.StringValue lang_tag = 4; + // The location set by the user. + google.protobuf.StringValue location = 5; + // The timezone set by the user. + google.protobuf.StringValue timezone = 6; +} + +// Update fields in a given group. +message UpdateGroupRequest { + // The ID of the group to update. + string group_id = 1; + // Name. + google.protobuf.StringValue name = 2; + // Description string. + google.protobuf.StringValue description = 3; + // Lang tag. + google.protobuf.StringValue lang_tag = 4; + // Avatar URL. + google.protobuf.StringValue avatar_url = 5; + // Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin. + google.protobuf.BoolValue open = 6; +} + +// A single group-role pair. +message UserGroupList_UserGroup { + // The group role status. + enum State { + // The user is a superadmin with full control of the group. + SUPERADMIN = 0; + // The user is an admin with additional privileges. + ADMIN = 1; + // The user is a regular member. + MEMBER = 2; + // The user has requested to join the group + JOIN_REQUEST = 3; + } + + // Group. + Group group = 1; + // The user's relationship to the group. + google.protobuf.Int32Value state = 2; +} + +// A list of groups belonging to a user, along with the user's role in each group. +message UserGroupList { + // Group-role pairs for a user. + repeated UserGroupList_UserGroup user_groups = 1; + // Cursor for the next page of results, if any. + string cursor = 2; +} + +// A collection of zero or more users. +message Users { + // The User objects. + repeated User users = 1; +} + +// Apple IAP Purchases validation request +message ValidatePurchaseAppleRequest { + // Base64 encoded Apple receipt data payload. + string receipt = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Apple Subscription validation request +message ValidateSubscriptionAppleRequest { + // Base64 encoded Apple receipt data payload. + string receipt = 1; + // Persist the subscription. + google.protobuf.BoolValue persist = 2; +} + +// Google IAP Purchase validation request +message ValidatePurchaseGoogleRequest { + // JSON encoded Google purchase payload. + string purchase = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Google Subscription validation request +message ValidateSubscriptionGoogleRequest { + // JSON encoded Google purchase payload. + string receipt = 1; + // Persist the subscription. + google.protobuf.BoolValue persist = 2; +} + +// Huawei IAP Purchase validation request +message ValidatePurchaseHuaweiRequest { + // JSON encoded Huawei InAppPurchaseData. + string purchase = 1; + // InAppPurchaseData signature. + string signature = 2; + // Persist the purchase + google.protobuf.BoolValue persist = 3; +} + +// Facebook Instant IAP Purchase validation request +message ValidatePurchaseFacebookInstantRequest { + // Base64 encoded Facebook Instant signedRequest receipt data payload. + string signed_request = 1; + // Persist the purchase + google.protobuf.BoolValue persist = 2; +} + +// Validation Provider, +enum StoreProvider { + // Apple App Store + APPLE_APP_STORE = 0; + // Google Play Store + GOOGLE_PLAY_STORE = 1; + // Huawei App Gallery + HUAWEI_APP_GALLERY = 2; + // Facebook Instant Store + FACEBOOK_INSTANT_STORE = 3; +} + +// Environment where a purchase/subscription took place, +enum StoreEnvironment { + // Unknown environment. + UNKNOWN = 0; + // Sandbox/test environment. + SANDBOX = 1; + // Production environment. + PRODUCTION = 2; +} + +// Validated Purchase stored by Nakama. +message ValidatedPurchase { + // Purchase User ID. + string user_id = 1; + // Purchase Product ID. + string product_id = 2; + // Purchase Transaction ID. + string transaction_id = 3; + // Store identifier + StoreProvider store = 4; + // Timestamp when the purchase was done. + google.protobuf.Timestamp purchase_time = 5; + // Timestamp when the receipt validation was stored in DB. + google.protobuf.Timestamp create_time = 6; + // Timestamp when the receipt validation was updated in DB. + google.protobuf.Timestamp update_time = 7; + // Timestamp when the purchase was refunded. Set to UNIX + google.protobuf.Timestamp refund_time = 8; + // Raw provider validation response. + string provider_response = 9; + // Whether the purchase was done in production or sandbox environment. + StoreEnvironment environment = 10; + // Whether the purchase had already been validated by Nakama before. + bool seen_before = 11; +} + +// Validate IAP response. +message ValidatePurchaseResponse { + // Newly seen validated purchases. + repeated ValidatedPurchase validated_purchases = 1; +} + +message ValidatedSubscription { + // Subscription User ID. + string user_id = 1; + // Purchase Product ID. + string product_id = 2; + // Purchase Original transaction ID (we only keep track of the original subscription, not subsequent renewals). + string original_transaction_id = 3; + // Store identifier + StoreProvider store = 4; + // UNIX Timestamp when the purchase was done. + google.protobuf.Timestamp purchase_time = 5; + // UNIX Timestamp when the receipt validation was stored in DB. + google.protobuf.Timestamp create_time = 6; + // UNIX Timestamp when the receipt validation was updated in DB. + google.protobuf.Timestamp update_time = 7; + // Whether the purchase was done in production or sandbox environment. + StoreEnvironment environment = 8; + // Subscription expiration time. The subscription can still be auto-renewed to extend the expiration time further. + google.protobuf.Timestamp expiry_time = 9; + // Subscription refund time. If this time is set, the subscription was refunded. + google.protobuf.Timestamp refund_time = 10; + // Raw provider validation response body. + string provider_response = 11; + // Raw provider notification body. + string provider_notification = 12; + // Whether the subscription is currently active or not. + bool active = 13; +} + +// Validate Subscription response. +message ValidateSubscriptionResponse { + ValidatedSubscription validated_subscription = 1; +} + +// A list of validated purchases stored by Nakama. +message PurchaseList { + // Stored validated purchases. + repeated ValidatedPurchase validated_purchases = 1; + // The cursor to send when retrieving the next page, if any. + string cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; +} + +// A list of validated subscriptions stored by Nakama. +message SubscriptionList { + // Stored validated subscriptions. + repeated ValidatedSubscription validated_subscriptions = 1; + // The cursor to send when retrieving the next page, if any. + string cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; +} + +// Record values to write. +message WriteLeaderboardRecordRequest_LeaderboardRecordWrite { + // The score value to submit. + int64 score = 1; + // An optional secondary value. + int64 subscore = 2; + // Optional record metadata. + string metadata = 3; + // Operator override. + Operator operator = 4; +} + +// A request to submit a score to a leaderboard. +message WriteLeaderboardRecordRequest { + // The ID of the leaderboard to write to. + string leaderboard_id = 1; + // Record input. + WriteLeaderboardRecordRequest_LeaderboardRecordWrite record = 2; +} + +// The object to store. +message WriteStorageObject { + // The collection to store the object. + string collection = 1; + // The key for the object within the collection. + string key = 2; + // The value of the object. + string value = 3; + // The version hash of the object to check. Possible values are: ["", "*", "#hash#"]. + string version = 4; // if-match and if-none-match + // The read access permissions for the object. + google.protobuf.Int32Value permission_read = 5; + // The write access permissions for the object. + google.protobuf.Int32Value permission_write = 6; +} + +// Write objects to the storage engine. +message WriteStorageObjectsRequest { + // The objects to store on the server. + repeated WriteStorageObject objects = 1; +} + +// Record values to write. +message WriteTournamentRecordRequest_TournamentRecordWrite { + // The score value to submit. + int64 score = 1; + // An optional secondary value. + int64 subscore = 2; + // A JSON object of additional properties (optional). + string metadata = 3; + // Operator override. + Operator operator = 4; +} + +// A request to submit a score to a tournament. +message WriteTournamentRecordRequest { + // The tournament ID to write the record for. + string tournament_id = 1; + // Record input. + WriteTournamentRecordRequest_TournamentRecordWrite record = 2; +} + +// Operator that can be used to override the one set in the leaderboard. +enum Operator { + // Do not override the leaderboard operator. + NO_OVERRIDE = 0; + // Override the leaderboard operator with BEST. + BEST = 1; + // Override the leaderboard operator with SET. + SET = 2; + // Override the leaderboard operator with INCREMENT. + INCREMENT = 3; + // Override the leaderboard operator with DECREMENT. + DECREMENT = 4; +} + +// A request to list parties. +message ListPartiesRequest { + // Limit the number of returned parties. + google.protobuf.Int32Value limit = 1; + // Optionally filter by open/closed parties. + google.protobuf.BoolValue open = 2; + // Arbitrary label query. + google.protobuf.StringValue query = 3; + // Cursor for the next page of results, if any. + google.protobuf.StringValue cursor = 4; +} + +// Incoming information about a party. +message Party { + // Unique party identifier. + string party_id = 1; + // Open flag. + bool open = 2; + // Hidden flag. + bool hidden = 3; + // Maximum number of party members. + int32 max_size = 4; + // The party label, if any. + string label = 5; +} + +// A list of realtime matches. +message PartyList { + // A number of parties corresponding to a list operation. + repeated Party parties = 1; + // A cursor to send when retrieving the next page, if any. + string cursor = 2; +} diff --git a/cmd/codegen-modular/unreal-sdk/protos/realtime.proto b/cmd/codegen-modular/unreal-sdk/protos/realtime.proto new file mode 100644 index 000000000..aac5d1b24 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/protos/realtime.proto @@ -0,0 +1,691 @@ +// Copyright 2019 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * The realtime protocol for Nakama server. + */ +syntax = "proto3"; + +package nakama.realtime; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "api/api.proto"; + +option go_package = "github.com/heroiclabs/nakama-common/rtapi"; + +option java_multiple_files = true; +option java_outer_classname = "NakamaRealtime"; +option java_package = "com.heroiclabs.nakama.rtapi"; + +option csharp_namespace = "Nakama.Protobuf"; + +// An envelope for a realtime message. +message Envelope { + string cid = 1; + oneof message { + // A response from a channel join operation. + Channel channel = 2; + // Join a realtime chat channel. + ChannelJoin channel_join = 3; + // Leave a realtime chat channel. + ChannelLeave channel_leave = 4; + // An incoming message on a realtime chat channel. + api.ChannelMessage channel_message = 5; + // An acknowledgement received in response to sending a message on a chat channel. + ChannelMessageAck channel_message_ack = 6; + // Send a message to a realtime chat channel. + ChannelMessageSend channel_message_send = 7; + // Update a message previously sent to a realtime chat channel. + ChannelMessageUpdate channel_message_update = 8; + // Remove a message previously sent to a realtime chat channel. + ChannelMessageRemove channel_message_remove = 9; + // Presence update for a particular realtime chat channel. + ChannelPresenceEvent channel_presence_event = 10; + // Describes an error which occurred on the server. + Error error = 11; + // Incoming information about a realtime match. + Match match = 12; + // A client to server request to create a realtime match. + MatchCreate match_create = 13; + // Incoming realtime match data delivered from the server. + MatchData match_data = 14; + // A client to server request to send data to a realtime match. + MatchDataSend match_data_send = 15; + // A client to server request to join a realtime match. + MatchJoin match_join = 16; + // A client to server request to leave a realtime match. + MatchLeave match_leave = 17; + // Presence update for a particular realtime match. + MatchPresenceEvent match_presence_event = 18; + // Submit a new matchmaking process request. + MatchmakerAdd matchmaker_add = 19; + // A successful matchmaking result. + MatchmakerMatched matchmaker_matched = 20; + // Cancel a matchmaking process using a ticket. + MatchmakerRemove matchmaker_remove = 21; + // A response from starting a new matchmaking process. + MatchmakerTicket matchmaker_ticket = 22; + // Notifications send by the server. + Notifications notifications = 23; + // RPC call or response. + api.Rpc rpc = 24; + // An incoming status snapshot for some set of users. + Status status = 25; + // Start following some set of users to receive their status updates. + StatusFollow status_follow = 26; + // An incoming status update. + StatusPresenceEvent status_presence_event = 27; + // Stop following some set of users to no longer receive their status updates. + StatusUnfollow status_unfollow = 28; + // Set the user's own status. + StatusUpdate status_update = 29; + // A data message delivered over a stream. + StreamData stream_data = 30; + // Presence update for a particular stream. + StreamPresenceEvent stream_presence_event = 31; + // Application-level heartbeat and connection check. + Ping ping = 32; + // Application-level heartbeat and connection check response. + Pong pong = 33; + // Incoming information about a party. + Party party = 34; + // Create a party. + PartyCreate party_create = 35; + // Join a party, or request to join if the party is not open. + PartyJoin party_join = 36; + // Leave a party. + PartyLeave party_leave = 37; + // Promote a new party leader. + PartyPromote party_promote = 38; + // Announcement of a new party leader. + PartyLeader party_leader = 39; + // Accept a request to join. + PartyAccept party_accept = 40; + // Kick a party member, or decline a request to join. + PartyRemove party_remove = 41; + // End a party, kicking all party members and closing it. + PartyClose party_close = 42; + // Request a list of pending join requests for a party. + PartyJoinRequestList party_join_request_list = 43; + // Incoming notification for one or more new presences attempting to join the party. + PartyJoinRequest party_join_request = 44; + // Begin matchmaking as a party. + PartyMatchmakerAdd party_matchmaker_add = 45; + // Cancel a party matchmaking process using a ticket. + PartyMatchmakerRemove party_matchmaker_remove = 46; + // A response from starting a new party matchmaking process. + PartyMatchmakerTicket party_matchmaker_ticket = 47; + // Incoming party data delivered from the server. + PartyData party_data = 48; + // A client to server request to send data to a party. + PartyDataSend party_data_send = 49; + // Presence update for a particular party. + PartyPresenceEvent party_presence_event = 50; + // Update Party label and whether it's open or closed. + PartyUpdate party_update = 51; + } +} + +// A user session associated to a stream, usually through a list operation or a join/leave event. +message UserPresence { + // The user this presence belongs to. + string user_id = 1; + // A unique session ID identifying the particular connection, because the user may have many. + string session_id = 2; + // The username for display purposes. + string username = 3; + // Whether this presence generates persistent data/messages, if applicable for the stream type. + bool persistence = 4; + // A user-set status message for this stream, if applicable. + google.protobuf.StringValue status = 5; +} + +// A realtime chat channel. +message Channel { + // The ID of the channel. + string id = 1; + // The users currently in the channel. + repeated UserPresence presences = 2; + // A reference to the current user's presence in the channel. + UserPresence self = 3; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 4; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 5; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 6; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 7; +} + +// Join operation for a realtime chat channel. +message ChannelJoin { + // The type of chat channel. + enum Type { + // Default case. Assumed as ROOM type. + TYPE_UNSPECIFIED = 0; + // A room which anyone can join to chat. + ROOM = 1; + // A private channel for 1-on-1 chat. + DIRECT_MESSAGE = 2; + // A channel for group chat. + GROUP = 3; + } + + // The user ID to DM with, group ID to chat with, or room channel name to join. + string target = 1; + // The type of the chat channel. + int32 type = 2; // one of "ChannelId.Type". + // Whether messages sent on this channel should be persistent. + google.protobuf.BoolValue persistence = 3; + // Whether the user should appear in the channel's presence list and events. + google.protobuf.BoolValue hidden = 4; +} + +// Leave a realtime channel. +message ChannelLeave { + // The ID of the channel to leave. + string channel_id = 1; +} + +// A receipt reply from a channel message send operation. +message ChannelMessageAck { + // The channel the message was sent to. + string channel_id = 1; + // The unique ID assigned to the message. + string message_id = 2; + // The code representing a message type or category. + google.protobuf.Int32Value code = 3; + // Username of the message sender. + string username = 4; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was created. + google.protobuf.Timestamp create_time = 5; + // The UNIX time (for gRPC clients) or ISO string (for REST clients) when the message was last updated. + google.protobuf.Timestamp update_time = 6; + // True if the message was persisted to the channel's history, false otherwise. + google.protobuf.BoolValue persistent = 7; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 8; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 9; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 10; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 11; +} + +// Send a message to a realtime channel. +message ChannelMessageSend { + // The channel to sent to. + string channel_id = 1; + // Message content. + string content = 2; +} + +// Update a message previously sent to a realtime channel. +message ChannelMessageUpdate { + // The channel the message was sent to. + string channel_id = 1; + // The ID assigned to the message to update. + string message_id = 2; + // New message content. + string content = 3; +} + +// Remove a message previously sent to a realtime channel. +message ChannelMessageRemove { + // The channel the message was sent to. + string channel_id = 1; + // The ID assigned to the message to update. + string message_id = 2; +} + +// A set of joins and leaves on a particular channel. +message ChannelPresenceEvent { + // The channel identifier this event is for. + string channel_id = 1; + // Presences joining the channel as part of this event, if any. + repeated UserPresence joins = 2; + // Presences leaving the channel as part of this event, if any. + repeated UserPresence leaves = 3; + // The name of the chat room, or an empty string if this message was not sent through a chat room. + string room_name = 4; + // The ID of the group, or an empty string if this message was not sent through a group channel. + string group_id = 5; + // The ID of the first DM user, or an empty string if this message was not sent through a DM chat. + string user_id_one = 6; + // The ID of the second DM user, or an empty string if this message was not sent through a DM chat. + string user_id_two = 7; +} + +// A logical error which may occur on the server. +message Error { + // The selection of possible error codes. + enum Code { + // An unexpected result from the server. + RUNTIME_EXCEPTION = 0; + // The server received a message which is not recognised. + UNRECOGNIZED_PAYLOAD = 1; + // A message was expected but contains no content. + MISSING_PAYLOAD = 2; + // Fields in the message have an invalid format. + BAD_INPUT = 3; + // The match id was not found. + MATCH_NOT_FOUND = 4; + // The match join was rejected. + MATCH_JOIN_REJECTED = 5; + // The runtime function does not exist on the server. + RUNTIME_FUNCTION_NOT_FOUND = 6; + // The runtime function executed with an error. + RUNTIME_FUNCTION_EXCEPTION = 7; + } + + // The error code which should be one of "Error.Code" enums. + int32 code = 1; + // A message in English to help developers debug the response. + string message = 2; + // Additional error details which may be different for each response. + map context = 3; +} + +// A realtime match. +message Match { + // The match unique ID. + string match_id = 1; + // True if it's an server-managed authoritative match, false otherwise. + bool authoritative = 2; + // Match label, if any. + google.protobuf.StringValue label = 3; + // The number of users currently in the match. + int32 size = 4; + // The users currently in the match. + repeated UserPresence presences = 5; + // A reference to the current user's presence in the match. + UserPresence self = 6; +} + +// Create a new realtime match. +message MatchCreate { + // Optional name to use when creating the match. + string name = 1; +} + +// Realtime match data received from the server. +message MatchData { + // The match unique ID. + string match_id = 1; + // A reference to the user presence that sent this data, if any. + UserPresence presence = 2; + // Op code value. + int64 op_code = 3; + // Data payload, if any. + bytes data = 4; + // True if this data was delivered reliably, false otherwise. + bool reliable = 5; +} + +// Send realtime match data to the server. +message MatchDataSend { + // The match unique ID. + string match_id = 1; + // Op code value. + int64 op_code = 2; + // Data payload, if any. + bytes data = 3; + // List of presences in the match to deliver to, if filtering is required. Otherwise deliver to everyone in the match. + repeated UserPresence presences = 4; + // True if the data should be sent reliably, false otherwise. + bool reliable = 5; +} + +// Join an existing realtime match. +message MatchJoin { + oneof id { + // The match unique ID. + string match_id = 1; + // A matchmaking result token. + string token = 2; + } + // An optional set of key-value metadata pairs to be passed to the match handler, if any. + map metadata = 3; +} + +// Leave a realtime match. +message MatchLeave { + // The match unique ID. + string match_id = 1; +} + +// A set of joins and leaves on a particular realtime match. +message MatchPresenceEvent { + // The match unique ID. + string match_id = 1; + // User presences that have just joined the match. + repeated UserPresence joins = 2; + // User presences that have just left the match. + repeated UserPresence leaves = 3; +} + +// Start a new matchmaking process. +message MatchmakerAdd { + // Minimum total user count to match together. + int32 min_count = 1; + // Maximum total user count to match together. + int32 max_count = 2; + // Filter query used to identify suitable users. + string query = 3; + // String properties. + map string_properties = 4; + // Numeric properties. + map numeric_properties = 5; + // Optional multiple of the count that must be satisfied. + google.protobuf.Int32Value count_multiple = 6; +} + +message MatchmakerMatched_MatchmakerUser { + // User info. + UserPresence presence = 1; + // Party identifier, if this user was matched as a party member. + string party_id = 2; + // String properties. + map string_properties = 5; + // Numeric properties. + map numeric_properties = 6; +} + +// A successful matchmaking result. +message MatchmakerMatched { + // The matchmaking ticket that has completed. + string ticket = 1; + // The match token or match ID to join. + oneof id { + // Match ID. + string match_id = 2; + // Match join token. + string token = 3; + } + // The users that have been matched together, and information about their matchmaking data. + repeated MatchmakerMatched_MatchmakerUser users = 4; + // A reference to the current user and their properties. + MatchmakerMatched_MatchmakerUser self = 5; +} + +// Cancel an existing ongoing matchmaking process. +message MatchmakerRemove { + // The ticket to cancel. + string ticket = 1; +} + +// A ticket representing a new matchmaking process. +message MatchmakerTicket { + // The ticket that can be used to cancel matchmaking. + string ticket = 1; +} + +// A collection of zero or more notifications. +message Notifications { + // Collection of notifications. + repeated api.Notification notifications = 1; +} + +// Incoming information about a party. +message Party { + // Unique party identifier. + string party_id = 1; + // Open flag. + bool open = 2; + // Hidden flag. + bool hidden = 3; + // Maximum number of party members. + int32 max_size = 4; + // Self. + UserPresence self = 5; + // Leader. + UserPresence leader = 6; + // All current party members. + repeated UserPresence presences = 7; + // Label for party listing. + string label = 8; +} + +// Create a party. +message PartyCreate { + // Whether or not the party will require join requests to be approved by the party leader. + bool open = 1; + // Maximum number of party members. + int32 max_size = 2; + // Label + string label = 3; + // Whether the party is visible in party listings. + bool hidden = 4; +} + +// Update a party label. +message PartyUpdate { + // Party ID. + string party_id = 1; + // Label to set. + string label = 2; + // Change the party to open or closed. + bool open = 3; + // Whether the party is visible in party listings. + bool hidden = 4; +} + +// Join a party, or request to join if the party is not open. +message PartyJoin { + // Party ID to join. + string party_id = 1; +} + +// Leave a party. +message PartyLeave { + // Party ID to leave. + string party_id = 1; +} + +// Promote a new party leader. +message PartyPromote { + // Party ID to promote a new leader for. + string party_id = 1; + // The presence of an existing party member to promote as the new leader. + UserPresence presence = 2; +} + +// Announcement of a new party leader. +message PartyLeader { + // Party ID to announce the new leader for. + string party_id = 1; + // The presence of the new party leader. + UserPresence presence = 2; +} + +// Accept a request to join. +message PartyAccept { + // Party ID to accept a join request for. + string party_id = 1; + // The presence to accept as a party member. + UserPresence presence = 2; +} + +// Kick a party member, or decline a request to join. +message PartyRemove { + // Party ID to remove/reject from. + string party_id = 1; + // The presence to remove or reject. + UserPresence presence = 2; +} + +// End a party, kicking all party members and closing it. +message PartyClose { + // Party ID to close. + string party_id = 1; +} + +// Request a list of pending join requests for a party. +message PartyJoinRequestList { + // Party ID to get a list of join requests for. + string party_id = 1; +} + +// Incoming notification for one or more new presences attempting to join the party. +message PartyJoinRequest { + // Party ID these presences are attempting to join. + string party_id = 1; + // Presences attempting to join. + repeated UserPresence presences = 2; +} + +// Begin matchmaking as a party. +message PartyMatchmakerAdd { + // Party ID. + string party_id = 1; + // Minimum total user count to match together. + int32 min_count = 2; + // Maximum total user count to match together. + int32 max_count = 3; + // Filter query used to identify suitable users. + string query = 4; + // String properties. + map string_properties = 5; + // Numeric properties. + map numeric_properties = 6; + // Optional multiple of the count that must be satisfied. + google.protobuf.Int32Value count_multiple = 7; +} + +// Cancel a party matchmaking process using a ticket. +message PartyMatchmakerRemove { + // Party ID. + string party_id = 1; + // The ticket to cancel. + string ticket = 2; +} + +// A response from starting a new party matchmaking process. +message PartyMatchmakerTicket { + // Party ID. + string party_id = 1; + // The ticket that can be used to cancel matchmaking. + string ticket = 2; +} + +// Incoming party data delivered from the server. +message PartyData { + // The party ID. + string party_id = 1; + // A reference to the user presence that sent this data, if any. + UserPresence presence = 2; + // Op code value. + int64 op_code = 3; + // Data payload, if any. + bytes data = 4; +} + +// Send data to a party. +message PartyDataSend { + // Party ID to send to. + string party_id = 1; + // Op code value. + int64 op_code = 2; + // Data payload, if any. + bytes data = 3; +} + +// Presence update for a particular party. +message PartyPresenceEvent { + // The party ID. + string party_id = 1; + // User presences that have just joined the party. + repeated UserPresence joins = 2; + // User presences that have just left the party. + repeated UserPresence leaves = 3; +} + + +// Application-level heartbeat and connection check. +message Ping {} + +// Application-level heartbeat and connection check response. +message Pong {} + +// A snapshot of statuses for some set of users. +message Status { + // User statuses. + repeated UserPresence presences = 1; +} + +// Start receiving status updates for some set of users. +message StatusFollow { + // User IDs to follow. + repeated string user_ids = 1; + // Usernames to follow. + repeated string usernames = 2; +} + +// A batch of status updates for a given user. +message StatusPresenceEvent { + // New statuses for the user. + repeated UserPresence joins = 2; + // Previous statuses for the user. + repeated UserPresence leaves = 3; +} + +// Stop receiving status updates for some set of users. +message StatusUnfollow { + // Users to unfollow. + repeated string user_ids = 1; +} + +// Set the user's own status. +message StatusUpdate { + // Status string to set, if not present the user will appear offline. + google.protobuf.StringValue status = 1; +} + +// Represents identifying information for a stream. +message Stream { + // Mode identifies the type of stream. + int32 mode = 1; + // Subject is the primary identifier, if any. + string subject = 2; + // Subcontext is a secondary identifier, if any. + string subcontext = 3; + // The label is an arbitrary identifying string, if the stream has one. + string label = 4; +} + +// A data message delivered over a stream. +message StreamData { + // The stream this data message relates to. + Stream stream = 1; + // The sender, if any. + UserPresence sender = 2; + // Arbitrary contents of the data message. + string data = 3; + // True if this data was delivered reliably, false otherwise. + bool reliable = 4; +} + +// A set of joins and leaves on a particular stream. +message StreamPresenceEvent { + // The stream this event relates to. + Stream stream = 1; + // Presences joining the stream as part of this event, if any. + repeated UserPresence joins = 2; + // Presences leaving the stream as part of this event, if any. + repeated UserPresence leaves = 3; +} + diff --git a/cmd/codegen-modular/unreal-sdk/protos/satori-api.proto b/cmd/codegen-modular/unreal-sdk/protos/satori-api.proto new file mode 100644 index 000000000..38c108ca5 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/protos/satori-api.proto @@ -0,0 +1,229 @@ +// Copyright 2022 GameUp Online, Inc. d/b/a Heroic Labs +// +// NOTICE: All information contained herein is, and remains the property of Heroic +// Labs. and its suppliers, if any. The intellectual and technical concepts contained +// herein are proprietary to Heroic Labs. and its suppliers and may be covered by U.S. +// and Foreign Patents, patents in process, and are protected by trade secret or +// copyright law. Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained from Heroic Labs. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/heroiclabs/satori/api"; + +option java_multiple_files = true; +option java_outer_classname = "SatoriApi"; +option java_package = "com.heroiclabs.satori.api"; + +/** + * The public client API for the Satori server. + */ +package satori.api; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "Satori Server API"; + version: "1.0"; + contact: { + name: "Heroic Labs"; + url: "https://heroiclabs.com"; + email: "hello@heroiclabs.com"; + }; + }; + host: "127.0.0.1:7450"; + schemes: HTTP; + consumes: "application/json"; + produces: "application/json"; + security_definitions: { + security: { + key: "BasicAuth"; + value: { + type: TYPE_BASIC; + } + } + security: { + // Made up security so we can apply "Bearer " + key: "BearerJwt"; + value: {}; + } + } + // Default security definition. + security: { + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } +}; + +/** + * The GRPC protocol service for Satori built with GRPC. + */ +service Satori { + // Authenticate against the server. + rpc Authenticate (AuthenticateRequest) returns (Session) { + option (google.api.http) = { + post: "/v1/authenticate", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. + rpc AuthenticateLogout (AuthenticateLogoutRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/authenticate/logout", + body: "*" + }; + } + + // Refresh a user's session using a refresh token retrieved from a previous authentication request. + rpc AuthenticateRefresh (AuthenticateRefreshRequest) returns (Session) { + option (google.api.http) = { + post: "/v1/authenticate/refresh", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + } + }; + } + + // Delete the caller's identity and associated data. + rpc DeleteIdentity(google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v1/identity"; + } + + // Publish an event for this session. + rpc Event(EventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/event", + body: "*" + }; + } + + // Publish server events for multiple distinct identities. + rpc ServerEvent(EventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/server-event", + body: "*" + }; + } + + // Get or list all available experiments for this identity. + rpc GetExperiments (GetExperimentsRequest) returns (ExperimentList) { + option (google.api.http).get = "/v1/experiment"; + } + + // List all available flags and their value overrides for this identity. + rpc GetFlagOverrides (GetFlagsRequest) returns (FlagOverrideList) { + option (google.api.http).get = "/v1/flag/override"; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // List all available flags for this identity. + rpc GetFlags (GetFlagsRequest) returns (FlagList) { + option (google.api.http).get = "/v1/flag"; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + // Either HTTP key in query param or Bearer authentication. + security: { + security_requirement: { + key: "BasicAuth"; + value: {}; + } + security_requirement: { + key: "BearerJwt"; + value: {}; + } + } + }; + } + + // List available live events. + rpc GetLiveEvents (GetLiveEventsRequest) returns (LiveEventList) { + option (google.api.http).get = "/v1/live-event"; + } + + // Join an 'explicit join' live event. + rpc JoinLiveEvent(JoinLiveEventRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/live-event/{id}/participation" + }; + } + + // A healthcheck which load balancers can use to check the service. + rpc Healthcheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/healthcheck"; + } + + // Enrich/replace the current session with new identifier. + rpc Identify(IdentifyRequest) returns (Session) { + option (google.api.http) = { + put: "/v1/identify", + body: "*" + }; + } + + // List properties associated with this identity. + rpc ListProperties (google.protobuf.Empty) returns (Properties) { + option (google.api.http).get = "/v1/properties"; + } + + // A readycheck which load balancers can use to check the service. + rpc Readycheck (google.protobuf.Empty) returns (google.protobuf.Empty) { + option (google.api.http).get = "/readycheck"; + } + + // Update identity properties. + rpc UpdateProperties(UpdatePropertiesRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v1/properties", + body: "*" + }; + } + + // Get the list of messages for the identity. + rpc GetMessageList (GetMessageListRequest) returns (GetMessageListResponse) { + option (google.api.http).get = "/v1/message"; + } + + // Updates a message for an identity. + rpc UpdateMessage (UpdateMessageRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v1/message/{id}", + body: "*" + }; + } + + // Deletes a message for an identity. + rpc DeleteMessage (DeleteMessageRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v1/message/{id}"; + } +} diff --git a/cmd/codegen-modular/unreal-sdk/protos/satori-types.proto b/cmd/codegen-modular/unreal-sdk/protos/satori-types.proto new file mode 100644 index 000000000..155da3cd9 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/protos/satori-types.proto @@ -0,0 +1,368 @@ +// Copyright 2022 GameUp Online, Inc. d/b/a Heroic Labs +// +// NOTICE: All information contained herein is, and remains the property of Heroic +// Labs. and its suppliers, if any. The intellectual and technical concepts contained +// herein are proprietary to Heroic Labs. and its suppliers and may be covered by U.S. +// and Foreign Patents, patents in process, and are protected by trade secret or +// copyright law. Dissemination of this information or reproduction of this material +// is strictly forbidden unless prior written permission is obtained from Heroic Labs. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "github.com/heroiclabs/satori/api"; + +option java_multiple_files = true; +option java_outer_classname = "SatoriApi"; +option java_package = "com.heroiclabs.satori.api"; + +package satori.api; + +// Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. +message AuthenticateLogoutRequest { + // Session token to log out. + string token = 1; + // Refresh token to invalidate. + string refresh_token = 2; +} + +// Authenticate against the server with a refresh token. +message AuthenticateRefreshRequest { + // Refresh token. + string refresh_token = 1; +} + +// Authentication request +message AuthenticateRequest { + // Identity ID. Must be between eight and 128 characters (inclusive). + // Must be an alphanumeric string with only underscores and hyphens allowed. + string id = 1; + // Optional default properties to update with this call. + // If not set, properties are left as they are on the server. + map default = 2; + // Optional custom properties to update with this call. + // If not set, properties are left as they are on the server. + map custom = 3; + // Optional no_session modifies the request to only create/update + // an identity without creating a new session. If set to 'true' + // the response won't include a token and a refresh token. + bool no_session = 4; +} + +// The request to delete a scheduled message. +message DeleteMessageRequest { + // The identifier of the message. + string id = 1; +} + +// A single event. Usually, but not necessarily, part of a batch. +message Event { + // Event name. + string name = 1; + // Optional event ID assigned by the client, used to de-duplicate in retransmission scenarios. + // If not supplied the server will assign a randomly generated unique event identifier. + string id = 2; + // Event metadata, if any. + map metadata = 3; + // Optional value. + string value = 4; + // The time when the event was triggered on the producer side. + google.protobuf.Timestamp timestamp = 5; + // The identity id associated with the event. Ignored if the event is published as part of a session. + string identity_id = 6; + // The session id associated with the event. Ignored if the event is published as part of a session. + string session_id = 7; + // The session issued at associated with the event. Ignored if the event is published as part of a session. + int64 session_issued_at = 8; + // The session expires at associated with the event. Ignored if the event is published as part of a session. + int64 session_expires_at = 9; +} + +// Publish an event to the server +message EventRequest { + // Some number of events produced by a client. + repeated Event events = 1; +} + +// An experiment that this user is partaking. +message Experiment { + // Experiment name + string name = 1; + // Value associated with this Experiment. + string value = 2; + // The labels associated with this experiment. + repeated string labels = 3; + // Experiment Phase name + string phase_name = 4; + // Experiment Phase Variant name + string phase_variant_name = 5; +} + +// All experiments that this identity is involved with. +message ExperimentList { + // All experiments for this identity. + repeated Experiment experiments = 1; +} + +// The type of configuration that overrides a flag value. +enum ValueChangeReasonType { + VCR_UNKNOWN = 0; + VCR_FLAG_VARIANT = 1; + VCR_LIVE_EVENT = 2; + VCR_EXPERIMENT = 3; +} + +// The origin of change on a flag value. +message ValueChangeReason { + // The type of the configuration that declared the override. + ValueChangeReasonType type = 1; + // The name of the configuration that overrides the flag value. + string name = 2; + // The variant name of the configuration that overrides the flag value. + string variant_name = 3; +} + +// Feature flag available to the identity. +message Flag { + // Flag name + string name = 1; + // Value associated with this flag. + string value = 2; + // Whether the value for this flag has conditionally changed from the default state. + bool condition_changed = 3; + // The origin of change on the flag value returned. + ValueChangeReason change_reason = 4; + // The labels associated with this flag. + repeated string labels = 5; +} + +// All flags available to the identity +message FlagList { + // All flags + repeated Flag flags = 1; +} + +// The type of configuration that overrides a flag value. +enum FlagOverrideType { + FOT_FLAG = 0; + FOT_FLAG_VARIANT = 1; + FOT_LIVE_EVENT_FLAG = 2; + FOT_LIVE_EVENT_FLAG_VARIANT = 3; + FOT_EXPERIMENT_PHASE_VARIANT_FLAG = 4; +} + +// The details of a flag value override. +message FlagOverrideValue { + // The type of the configuration that declared the override. + FlagOverrideType type = 1; + // The name of the configuration that overrides the flag value. + string name = 2; + // The variant name of the configuration that overrides the flag value. + string variant_name = 3; + // The value of the configuration that overrides the flag. + string value = 4; + // The create time of the configuration that overrides the flag. + int64 create_time_sec = 5; +} + +// Feature flag available to the identity. +message FlagOverride { + // Flag name + string flag_name = 1; + // The list of configuration that affect the value of the flag. + repeated FlagOverrideValue overrides = 2; + // The labels associated with this flag. + repeated string labels = 3; +} + +// All flags available to the identity and their value overrides +message FlagOverrideList { + // All flags + repeated FlagOverride flags = 1; +} + +// Request to get all experiments data. +message GetExperimentsRequest { + // Experiment names; if empty string, all experiments are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Experiment; if empty string, all experiments are returned based on the remaining filters. + repeated string labels = 2; +} + +// Request to get all flags data. +message GetFlagsRequest { + // Flag names; if empty string, all flags are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Flag; if empty string, all flags are returned based on the remaining filters. + repeated string labels = 2; +} + +// Request to get all live events. +message GetLiveEventsRequest { + // Live event names; if empty string, all live events are returned based on the remaining filters. + repeated string names = 1; + // Label names that must be defined for each Live Event; if empty string, all live events are returned based on the remaining filters. + repeated string labels = 2; + // The maximum number of past event runs to return for each live event. + int32 past_run_count = 3; + // The maximum number of future event runs to return for each live event. + int32 future_run_count = 4; + // Start time of the time window filter to apply. + int64 start_time_sec = 5; + // End time of the time window filter to apply. + int64 end_time_sec = 6; +} + +// A scheduled message. +message Message { + // The identifier of the schedule. + string schedule_id = 1; + // The send time for the message. + int64 send_time = 2; + // A key-value pairs of metadata. + map metadata = 3; + // The time the message was created. + int64 create_time = 4; + // The time the message was updated. + int64 update_time = 5; + // The time the message was read by the client. + int64 read_time = 6; + // The time the message was consumed by the identity. + int64 consume_time = 7; + // The message's text. + string text = 8; + // The message's unique identifier. + string id = 9; + // The message's title. + string title = 10; + // The message's image url. + string image_url = 11; +} + +message GetMessageListRequest { + // Max number of messages to return. Between 1 and 100. + int32 limit = 1; + // True if listing should be older messages to newer, false if reverse. + bool forward = 2; + // A pagination cursor, if any. + string cursor = 3; + // A list of message identifiers to be returned. If these are given as inputs, the pagination will be disabled. + repeated string message_ids = 4; +} + +// A response containing all the messages for an identity. +message GetMessageListResponse { + // The list of messages. + repeated Message messages = 1; + // The cursor to send when retrieving the next page, if any. + string next_cursor = 2; + // The cursor to send when retrieving the previous page, if any. + string prev_cursor = 3; + // Cacheable cursor to list newer messages. Durable and designed to be stored, unlike next/prev cursors. + string cacheable_cursor = 4; +} + +// Enrich/replace the current session with a new ID. +message IdentifyRequest { + // Identity ID to enrich the current session and return a new session. Old session will no longer be usable. + string id = 1; + // Optional default properties to update with this call. + // If not set, properties are left as they are on the server. + map default = 2; + // Optional custom properties to update with this call. + // If not set, properties are left as they are on the server. + map custom = 3; +} + +// Request to join a 'explicit join' live event. +message JoinLiveEventRequest { + // Live event id to join. + string id = 1; +} + +// The status variants of a live event. +enum LiveEventStatus { + LES_UNKNOWN = 0; + LES_ACTIVE = 1; + LES_UPCOMING = 2; + LES_TERMINATED = 3; +} + +// A single live event. +message LiveEvent { + // Name. + string name = 1; + // Description. + string description = 2; + // Event value. + string value = 3; + // Start time of current event run. + int64 active_start_time_sec = 4; + // End time of current event run. + int64 active_end_time_sec = 5; + // The live event identifier. + string id = 6; + // Start time. + int64 start_time_sec = 7; + // End time, 0 if it repeats forever. + int64 end_time_sec = 8; + // Duration in seconds. + int64 duration_sec = 9; + // Reset CRON schedule, if configured. + string reset_cron = 10; + // The status of this live event run. + LiveEventStatus status = 11; + // The labels associated with this live event. + repeated string labels = 12; +} + +// List of Live events. +message LiveEventList { + // Live events. + repeated LiveEvent live_events = 1; + // Live events that require explicit join. + repeated LiveEvent explicit_join_live_events = 2; +} + +// Properties associated with an identity. +message Properties { + // Event default properties. + map default = 1; + // Event computed properties. + map computed = 2; + // Event custom properties. + map custom = 3; +} + +// A session. +message Session { + // Token credential. + string token = 1; + // Refresh token. + string refresh_token = 2; + // Properties associated with this identity. + Properties properties = 3; +} + +// Update Properties associated with this identity. +message UpdatePropertiesRequest { + // Event default properties. + map default = 1; + // Event custom properties. + map custom = 2; + // Informs the server to recompute the audience membership of the identity. + google.protobuf.BoolValue recompute = 3; +} + +// The request to update the status of a message. +message UpdateMessageRequest { + // The identifier of the messages. + string id = 1; + // The time the message was read at the client. + int64 read_time = 2; + // The time the message was consumed by the identity. + int64 consume_time = 3; +} diff --git a/cmd/codegen-modular/unreal-sdk/templates/Nakama.cpp.tmpl b/cmd/codegen-modular/unreal-sdk/templates/Nakama.cpp.tmpl new file mode 100644 index 000000000..11f1e14b1 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/templates/Nakama.cpp.tmpl @@ -0,0 +1,115 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "Nakama.h" + +{{ range $func := $.Funcs }} + +NAKAMAAPI_API void NakamaApi::{{ $func.Name }} ( + const FNakamaClientConfig& Config, + {{- range $idx, $param := $func.Params }} + {{ $param.Type }} {{ $param.Name }}, + {{- end }} + TFunction OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken +) noexcept +{ + FString Endpoint = TEXT("{{ $func.Locals.Endpoint }}") + // + // Fill Path Params + {{- range $pathParam := $func.Locals.PathParams }} + const FString Encoded_{{ $pathParam.Name }} = FGenericPlatformHttp::UrlEncode({{ $pathParam.Name }}); + Endpoint = Endpoint.Replace("{{ $pathParam.PathName }}", *Encoded_{{ $pathParam.Name }}); + {{- end }} + + // + // Fill Query Params + TArray QueryParams; + {{- range $queryParam := $func.Locals.QueryParams }} + {{- if $queryParam.Repeated }} + for ({{ $queryParam.IterationType }} Item : {{ $queryParam.Name }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $queryParam.Name }}={{ $queryParam.QueryFormat }}", {{ $queryParam.QueryValueSetter }}(Item))); + } + {{- else }} + QueryParams.Add(FString::Printf(TEXT("{{ $queryParam.Name }}={{ $queryParam.QueryFormat }}", {{ $queryParam.QueryValueSetter }}({{ $queryParam.Name }}))); + {{- end }} + {{- end }} + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } + + // + // Fill Body Params + TSharedPtr Body; + {{- range $bodyParam := $func.Locals.BodyParams }} + + {{- if $bodyParam.Repeated }} + if ({{ $bodyParam.Name }}.Num() > 0) + { + TArray> Array; + for ({{ $bodyParam.IterationType }} Item : {{ $bodyParam.Name }}) + { + Array.Add(MakeShared<{{ $bodyParam.JsonArrayType }}>(Item{{ $bodyParam.MaybeToJson }})); + } + Body->SetArrayField(TEXT("{{ $bodyParam.JsonFieldName }}"), Array); + } + {{- else if $bodyParam.IsMap }} + if ({{ $bodyParam.Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $bodyParam.Name }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("{{ $bodyParam.JsonFieldName }}"), MapObj); + } + {{- else }} + Body->{{ $bodyParam.JsonSetter }}(TEXT({{ $bodyParam.JsonFieldName }}), {{ $bodyParam.Name }}{{ $bodyParam.MaybeToJson }}); + {{- end }} + + {{- end }} + + // + // Make the request + MakeRequest( + Config, + Endpoint, + TEXT("{{ $func.Locals.Method }}"), + Body, + ENakamaRequestAuth::{{ $func.Locals.AuthType }}, + {{ $func.Locals.AuthKey }}, + [OnSuccess](TSharedPtr Json) + { + if (OnSuccess) + { + {{ if ne "" $func.Locals.ReturnTypeName }} + {{ $func.Locals.ReturnTypeName }} Result = {{ $func.Locals.ReturnTypeName }}::FromJson(Json); + OnSuccess(Result); + {{ else }} + OnSuccess(); + {{ end }} + } + }, + OnError, Timeout, CancellationToken); +} + +{{ end }} diff --git a/cmd/codegen-modular/unreal-sdk/templates/Nakama.h.tmpl b/cmd/codegen-modular/unreal-sdk/templates/Nakama.h.tmpl new file mode 100644 index 000000000..0b5faa3b1 --- /dev/null +++ b/cmd/codegen-modular/unreal-sdk/templates/Nakama.h.tmpl @@ -0,0 +1,53 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" +#include "HAL/ThreadSafeBool.h" +#include "NakamaTypes.h" + +namespace NakamaApi +{ + {{ range $func := $.Funcs }} + /* + * {{ $func.Comment }} + * + * Config The client configuration. + {{- range $func.Params }} + * {{ .Name }}{{ "\t" }}{{ .Comment }}{{ end }} + * OnSuccess Called when the operation succeeds. + * OnError Called when the operation fails. + * Timeout Request timeout in seconds. + * CancellationToken Set to true to cancel the in-flight request. + **/ + NAKAMAAPI_API void {{ $func.Name }}( + const FNakamaClientConfig& Config, + {{- range $idx, $param := $func.Params }} + {{ $param.Type }} {{ $param.Name }}, + {{- end }} + TFunction OnSuccess, + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false) + ) noexcept; + {{ end }} +} diff --git a/cmd/codegen-modular/unreal/nakama_api_vm.go b/cmd/codegen-modular/unreal/nakama_api_vm.go new file mode 100644 index 000000000..176f3fb9e --- /dev/null +++ b/cmd/codegen-modular/unreal/nakama_api_vm.go @@ -0,0 +1,74 @@ +package unreal + +import ( + "strings" + + "heroiclabs.com/modular-codegen/schema" +) + +// ApiViewModel is the unified data model consumed by all templates. +// Each template accesses the methods it needs via reflection. +type ApiViewModel struct { + methods []MethodImpl + messages []ResolvedMessage + enums []ResolvedEnum + rtOperations []ResolvedRtOperation + + // Naming configuration for shared (parameterized) templates. + name string // "Nakama" or "Satori" + typePrefix string // "FNakama" or "FSatori" + enumPrefix string // "ENakama" or "ESatori" +} + +func (vm ApiViewModel) Methods() []MethodImpl { return vm.methods } +func (vm ApiViewModel) Messages() []ResolvedMessage { return vm.messages } +func (vm ApiViewModel) Enums() []ResolvedEnum { return vm.enums } +func (vm ApiViewModel) RtOperations() []ResolvedRtOperation { return vm.rtOperations } + +// Naming accessors used by shared templates. +func (vm ApiViewModel) Name() string { return vm.name } +func (vm ApiViewModel) TypePrefix() string { return vm.typePrefix } +func (vm ApiViewModel) EnumPrefix() string { return vm.enumPrefix } + +// Derived naming: UE module export macros. +func (vm ApiViewModel) ApiExport() string { return strings.ToUpper(vm.name) + "API_API" } +func (vm ApiViewModel) HlExport() string { return strings.ToUpper(vm.name) + "_API" } +func (vm ApiViewModel) BpExport() string { return strings.ToUpper(vm.name) + "BLUEPRINTS_API" } + +// UniqueReturnTypes returns deduplicated return type names across all methods. +func (vm ApiViewModel) UniqueReturnTypes() []string { + seen := make(map[string]bool) + var result []string + for _, m := range vm.methods { + if m.HasReturn && !seen[m.ReturnType] { + seen[m.ReturnType] = true + result = append(result, m.ReturnType) + } + } + return result +} + +// MakeViewModelFactory returns a Production-compatible factory. +// flatten controls whether non-repeated message fields are inlined +// as individual function parameters in the high-level API. +func MakeViewModelFactory(typePfx, enumPfx string, flatten bool) func(any, schema.Api) (any, error) { + // Derive product name from type prefix: "FNakama" -> "Nakama" + name := strings.TrimPrefix(typePfx, "F") + + return func(tmRaw any, api schema.Api) (any, error) { + tm := tmRaw.(TypeMap) + var methods []MethodImpl + for _, rpc := range api.Rpcs { + methods = append(methods, resolveRpcMethods(rpc, tm, api, typePfx, enumPfx, flatten)...) + } + return ApiViewModel{ + methods: methods, + messages: resolveMessages(api, tm, typePfx, enumPfx), + enums: resolveEnums(api), + rtOperations: resolveRtOperations(api, tm, typePfx, enumPfx), + name: name, + typePrefix: typePfx, + enumPrefix: enumPfx, + }, nil + } +} diff --git a/cmd/codegen-modular/unreal/resolve.go b/cmd/codegen-modular/unreal/resolve.go new file mode 100644 index 000000000..82ebb95e4 --- /dev/null +++ b/cmd/codegen-modular/unreal/resolve.go @@ -0,0 +1,577 @@ +package unreal + +import ( + "fmt" + "log" + "strings" + + "github.com/golang-cz/textcase" + "heroiclabs.com/modular-codegen/schema" +) + +// resolveField maps a proto field to its fully-resolved target-language representation. +// This is the single resolution function for all contexts: struct declarations, +// function signatures, FromJson/ToJson, query params, and body serialization. +func resolveField(name, protoType string, repeated bool, comment string, tm TypeMap, api schema.Api, prefix, enumPfx string) ResolvedField { + // Strip package qualifier (e.g., "api.Notification" → "Notification") + // so cross-package proto references resolve correctly. + if idx := strings.LastIndex(protoType, "."); idx >= 0 { + stripped := protoType[idx+1:] + // Only strip if it's a package prefix, not a well-known type like "google.protobuf.StringValue" + if _, isEnum := api.EnumsByName[stripped]; isEnum { + protoType = stripped + } else if _, isMsg := api.MessagesByName[stripped]; isMsg { + protoType = stripped + } + } + + f := ResolvedField{ + Name: textcase.PascalCase(name), + JsonName: name, + Comment: comment, + IsRepeated: repeated, + } + + if entry, ok := tm.Resolve(protoType); ok { + // Function signature + f.ParamType = entry.Param + if repeated { + f.ParamType = entry.RepeatedParam + } + + // Struct declaration + f.FieldType = entry.FieldType + if repeated { + f.FieldType = entry.RepeatedFieldType + } + f.FieldDefault = entry.FieldDefault + + // JSON suffix form (for API method body template composition) + f.JsonMethod = entry.JsonMethod + f.EmptyCheck = entry.EmptyCheck + f.QueryFormat = entry.QueryFormat + + // JSON full form (for struct FromJson/ToJson templates) + f.JsonSetter = fmt.Sprintf("Set%sField", entry.JsonMethod) + f.JsonArrayValue = fmt.Sprintf("FJsonValue%s", entry.JsonArrayValue) + f.JsonGetter = entry.JsonGetter + f.CastFromJson = entry.CastFromJson + f.ArrayItemExpr = entry.ArrayItemExpr + f.IsBytes = protoType == "bytes" || protoType == "google.protobuf.BytesValue" + + // ToJson guard + if entry.NeedsEmptyGuard { + f.ToJsonGuard = "IsEmpty" + } else if strings.HasPrefix(protoType, "google.protobuf.") && strings.HasSuffix(protoType, "Value") && entry.EmptyCheck == "NonZero" { + f.ToJsonGuard = "NonZero" + } + return f + } + + if _, ok := api.EnumsByName[protoType]; ok { + f.IsEnum = true + f.EnumType = fmt.Sprintf("%s%s", enumPfx, protoType) + f.FieldType = f.EnumType + f.FieldDefault = " = static_cast<" + f.EnumType + ">(0)" + f.ParamType = "int32" + f.JsonMethod = "Number" + f.EmptyCheck = "NonZero" + f.QueryFormat = "%d" + f.JsonGetter = "GetIntegerField" + f.JsonSetter = "SetNumberField" + f.JsonArrayValue = "FJsonValueNumber" + f.ArrayItemExpr = "static_cast(Item->AsNumber())" + return f + } + + if _, ok := api.MessagesByName[protoType]; ok { + f.IsMessage = true + f.MessageType = fmt.Sprintf("%s%s", prefix, protoType) + f.FieldType = f.MessageType + f.ParamType = fmt.Sprintf("const %s&", f.MessageType) + if repeated { + f.FieldType = fmt.Sprintf("TArray<%s>", f.MessageType) + f.ParamType = fmt.Sprintf("const TArray<%s>&", f.MessageType) + } + f.JsonMethod = "Object" + f.JsonArrayValue = "FJsonValueObject" + f.JsonSetter = "SetObjectField" + f.EmptyCheck = "None" + return f + } + + log.Printf("warning: unknown type %q for field %s, defaulting to string", protoType, name) + if entry, ok := tm.Resolve("string"); ok { + f.ParamType = entry.Param + f.FieldType = entry.FieldType + f.FieldDefault = entry.FieldDefault + f.JsonMethod = entry.JsonMethod + f.EmptyCheck = entry.EmptyCheck + f.QueryFormat = entry.QueryFormat + f.JsonGetter = entry.JsonGetter + f.JsonSetter = fmt.Sprintf("Set%sField", entry.JsonMethod) + f.JsonArrayValue = fmt.Sprintf("FJsonValue%s", entry.JsonArrayValue) + f.ArrayItemExpr = entry.ArrayItemExpr + if entry.NeedsEmptyGuard { + f.ToJsonGuard = "IsEmpty" + } + } + return f +} + +// resolveMapField maps a proto map field to its resolved representation. +// Shared by resolveMessages, resolveRtOperations, and resolveRpcMethods. +func resolveMapField(name, protoType, comment string, tm TypeMap, prefix string) ResolvedField { + f := ResolvedField{ + Name: textcase.PascalCase(name), + JsonName: name, + Comment: comment, + IsMap: true, + } + if entry, ok := tm.Resolve(protoType); ok { + f.FieldType = entry.MapType + f.ParamType = fmt.Sprintf("const %s&", entry.MapType) + if entry.JsonMethod == "Number" { + f.MapValueSetter = "SetNumberField" + f.MapValueReader = "Pair.Value->AsNumber()" + } else { + f.MapValueSetter = "SetStringField" + f.MapValueReader = "Pair.Value->AsString()" + } + } else { + mapType := fmt.Sprintf("TMap", prefix, protoType) + f.FieldType = mapType + f.ParamType = fmt.Sprintf("const %s&", mapType) + f.MapValueSetter = "SetStringField" + f.MapValueReader = "Pair.Value->AsString()" + } + return f +} + +// resolveMessages converts all proto messages into pre-resolved structs. +func resolveMessages(api schema.Api, tm TypeMap, prefix, enumPfx string) []ResolvedMessage { + var result []ResolvedMessage + for _, msg := range api.Messages { + rm := ResolvedMessage{ + Name: msg.Name, + Comment: msg.Comment, + IsSession: msg.Name == "Session", + } + for _, f := range msg.Fields { + rm.Fields = append(rm.Fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix, enumPfx)) + } + for _, f := range msg.MapFields { + rm.MapFields = append(rm.MapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix)) + } + result = append(result, rm) + } + return result +} + +// resolveEnums converts all proto enums into pre-resolved enums. +func resolveEnums(api schema.Api) []ResolvedEnum { + var result []ResolvedEnum + for _, enum := range api.Enums { + re := ResolvedEnum{ + Name: enum.Name, + Comment: enum.Comment, + } + for i, f := range enum.Fields { + re.Fields = append(re.Fields, ResolvedEnumField{ + Name: f.Name, + Value: f.Integer, + IsLast: i == len(enum.Fields)-1, + }) + } + result = append(result, re) + } + return result +} + +// ueReservedNames maps identifiers that conflict with UE keywords. +var ueReservedNames = map[string]bool{ + "Destroy": true, "Type": true, "Class": true, "Object": true, +} + +// safeParamName adds a suffix to names that conflict with UE reserved words. +func safeParamName(name string) string { + if ueReservedNames[name] { + return name + "Param" + } + return name +} + +// resolveRtOperations builds resolved realtime operations from the Envelope +// message's oneof fields. +func resolveRtOperations(api schema.Api, tm TypeMap, prefix, enumPfx string) []ResolvedRtOperation { + envelope, ok := api.MessagesByName["Envelope"] + if !ok { + return nil + } + + var result []ResolvedRtOperation + for _, oneof := range envelope.OneofFields { + typeName := oneof.Name[strings.LastIndex(oneof.Name, ".")+1:] + msg, ok := api.MessagesByName[typeName] + if !ok { + continue + } + + op := ResolvedRtOperation{ + CaseName: oneof.Name, + MessageName: textcase.PascalCase(msg.Name), + Comment: msg.Comment, + } + + for _, f := range msg.Fields { + sf := resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix, enumPfx) + sf.Name = safeParamName(sf.Name) + op.Fields = append(op.Fields, sf) + } + for _, f := range msg.MapFields { + mf := resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix) + mf.Name = safeParamName(mf.Name) + op.MapFields = append(op.MapFields, mf) + } + for _, f := range msg.OneofFields { + op.OneofFields = append(op.OneofFields, ResolvedField{ + Name: safeParamName(textcase.PascalCase(f.Name)), + JsonName: f.Name, + FieldType: "FString", + FieldDefault: "", + ParamType: "const FString&", + JsonSetter: "SetStringField", + ToJsonGuard: "IsEmpty", + }) + } + + result = append(result, op) + } + return result +} + +// resolveRpcMethods converts an RPC into 1 or 2 MethodImpl entries: +// a primary variant (with Session if auth-gated) and an optional HttpKey overload. +func resolveRpcMethods(rpc *schema.VisitedRpc, tm TypeMap, api schema.Api, prefix, enumPfx string, flatten bool) []MethodImpl { + isAuth := strings.Contains(rpc.Name, "Authenticate") + isSessionRefresh := rpc.Name == "SessionRefresh" + needsSession := !isAuth && !isSessionRefresh + + allFields, allMapFields := resolveRpcFields(rpc, tm, api, prefix, enumPfx) + pathParams, queryFields, bodyNormals := classifyFields(rpc, allFields) + bodyMode, bodyFieldName, useStringBody, wildcardFields, wildcardMapFields := resolveBodyMode(rpc, bodyNormals, allFields, allMapFields) + highLevelParams, flattenedGroups := buildHighLevelParams(rpc, tm, api, prefix, enumPfx, flatten) + + // Return type. + hasReturn := rpc.ReturnType != nil + returnType := "" + asyncResultType := prefix + "Void" + if hasReturn { + returnType = rpc.ReturnType.Name + asyncResultType = prefix + returnType + } + + funcName := textcase.PascalCase(rpc.Name) + + // Authentication settings. + authType := "Bearer" + authToken := "Session.Token" + if isAuth || isSessionRefresh { + authType = "Basic" + authToken = `TEXT("")` + } + + // Build primary variant. + primary := MethodImpl{ + Name: funcName, + Comment: rpc.Comment, + HttpMethod: rpc.Method, + Endpoint: rpc.Endpoint, + AuthType: authType, + AuthToken: authToken, + Fields: allFields, + MapFields: allMapFields, + HasReturn: hasReturn, + ReturnType: returnType, + AsyncResultType: asyncResultType, + PathParams: pathParams, + QueryFields: queryFields, + BodyMode: bodyMode, + BodyFieldName: bodyFieldName, + WildcardFields: wildcardFields, + WildcardMapFields: wildcardMapFields, + UseStringBody: useStringBody, + HighLevelParams: highLevelParams, + FlattenedGroups: flattenedGroups, + } + + if needsSession { + primary.LeadParam = &Param{ + Type: "const FNakamaSession&", + Name: "Session", + Comment: "The session of the user.", + } + } + + result := []MethodImpl{primary} + + // Build HttpKey variant for session-gated RPCs. + if needsSession { + httpKey := MethodImpl{ + Name: funcName, + Comment: rpc.Comment + " (Server-to-server with HTTP key)", + LeadParam: &Param{ + Type: "const FString&", + Name: "HttpKey", + Comment: "The server HTTP key.", + }, + HttpMethod: rpc.Method, + Endpoint: rpc.Endpoint, + AuthType: "HttpKey", + AuthToken: "HttpKey", + Fields: excludeField(allFields, "http_key"), + MapFields: allMapFields, + HasReturn: hasReturn, + ReturnType: returnType, + AsyncResultType: asyncResultType, + PathParams: pathParams, + QueryFields: excludeField(queryFields, "http_key"), + BodyMode: bodyMode, + BodyFieldName: bodyFieldName, + WildcardFields: excludeField(wildcardFields, "http_key"), + WildcardMapFields: wildcardMapFields, + UseStringBody: useStringBody, + HighLevelParams: excludeHighLevelParam(highLevelParams, "HttpKey"), + FlattenedGroups: flattenedGroups, + } + result = append(result, httpKey) + } + + return result +} + +// resolveRpcFields resolves all request fields and map fields for an RPC. +func resolveRpcFields(rpc *schema.VisitedRpc, tm TypeMap, api schema.Api, prefix, enumPfx string) ([]ResolvedField, []ResolvedField) { + if rpc.RequestType == nil { + return nil, nil + } + + var fields []ResolvedField + for _, f := range rpc.RequestType.Fields { + fields = append(fields, resolveField(f.Name, f.Type, f.Repeated, f.Comment.Message(), tm, api, prefix, enumPfx)) + } + + var mapFields []ResolvedField + for _, f := range rpc.RequestType.MapFields { + mapFields = append(mapFields, resolveMapField(f.Name, f.Type, f.Comment.Message(), tm, prefix)) + } + + return fields, mapFields +} + +// classifyFields splits resolved fields into path params, query params, and body candidates. +func classifyFields(rpc *schema.VisitedRpc, allFields []ResolvedField) (pathParams, queryFields, bodyNormals []ResolvedField) { + pathSet := make(map[string]bool) + for _, p := range rpc.PathParams { + if p != "" { + pathSet[p] = true + } + } + + isGet := rpc.Method == "GET" + for _, f := range allFields { + switch { + case pathSet[f.JsonName]: + pathParams = append(pathParams, f) + case isQueryParam(f.JsonName, rpc.BodyField, isGet): + queryFields = append(queryFields, f) + default: + bodyNormals = append(bodyNormals, f) + } + } + return +} + +// resolveBodyMode determines how the request body should be serialized. +func resolveBodyMode(rpc *schema.VisitedRpc, bodyNormals, allFields, allMapFields []ResolvedField) (bodyMode, bodyFieldName string, useStringBody bool, wildcardFields, wildcardMapFields []ResolvedField) { + isGet := rpc.Method == "GET" + bodyField := rpc.BodyField + bodyMode = "none" + + switch { + case bodyField == "*" && !isGet: + bodyMode = "wildcard" + wildcardFields = bodyNormals + wildcardMapFields = allMapFields + case bodyField != "" && bodyField != "*": + for _, f := range bodyNormals { + if f.JsonName != bodyField { + continue + } + bodyFieldName = f.Name + if f.IsMessage { + bodyMode = "object" + } else { + bodyMode = "scalar" + useStringBody = true + for i := range allFields { + if allFields[i].JsonName == bodyField { + allFields[i].ParamType = "TSharedPtr" + break + } + } + } + break + } + } + return +} + +// buildHighLevelParams constructs the high-level API parameters with argument flattening. +func buildHighLevelParams(rpc *schema.VisitedRpc, tm TypeMap, api schema.Api, prefix, enumPfx string, flatten bool) ([]HighLevelParam, []FlattenedGroup) { + if rpc.RequestType == nil { + return nil, nil + } + + var highLevelParams []HighLevelParam + var flattenedGroups []FlattenedGroup + + // Identify which field indices are flattenable (non-repeated message types). + flatSet := make(map[int]bool) + if flatten { + for i, f := range rpc.RequestType.Fields { + if !f.Repeated { + if _, ok := api.MessagesByName[f.Type]; ok { + flatSet[i] = true + } + } + } + } + + // canDefault: flattened sub-fields get defaults only if all subsequent + // non-map fields are also flattened (C++ requires defaults at the end). + canDefault := func(fieldIdx int) bool { + for i := fieldIdx + 1; i < len(rpc.RequestType.Fields); i++ { + if !flatSet[i] { + return false + } + } + return true + } + + // Pass 1: non-map fields (flattening message types into sub-fields). + for i, f := range rpc.RequestType.Fields { + if flatSet[i] { + msg := api.MessagesByName[f.Type] + useDefaults := canDefault(i) + + group := FlattenedGroup{ + FieldName: textcase.PascalCase(f.Name), + FieldType: prefix + f.Type, + } + for _, sf := range msg.Fields { + resolved := resolveField(sf.Name, sf.Type, sf.Repeated, "", tm, api, prefix, enumPfx) + p := HighLevelParam{ + Name: resolved.Name, + ParamType: resolved.ParamType, + FieldType: resolved.FieldType, + FieldDefault: resolved.FieldDefault, + } + if useDefaults { + p.Default = " = {}" + } + highLevelParams = append(highLevelParams, p) + group.SubFields = append(group.SubFields, resolved.Name) + } + for _, sf := range msg.MapFields { + group.SubFields = append(group.SubFields, textcase.PascalCase(sf.Name)) + } + flattenedGroups = append(flattenedGroups, group) + } else if f.Name == rpc.BodyField { + if _, isMsg := api.MessagesByName[f.Type]; !isMsg { + highLevelParams = append(highLevelParams, HighLevelParam{ + Name: textcase.PascalCase(f.Name), + ParamType: "TSharedPtr", + FieldType: "TMap", + IsScalarBody: true, + }) + } else { + resolved := resolveField(f.Name, f.Type, f.Repeated, "", tm, api, prefix, enumPfx) + highLevelParams = append(highLevelParams, HighLevelParam{ + Name: resolved.Name, + ParamType: resolved.ParamType, + FieldType: resolved.FieldType, + }) + } + } else { + resolved := resolveField(f.Name, f.Type, f.Repeated, "", tm, api, prefix, enumPfx) + highLevelParams = append(highLevelParams, HighLevelParam{ + Name: resolved.Name, + ParamType: resolved.ParamType, + FieldType: resolved.FieldType, + FieldDefault: resolved.FieldDefault, + }) + } + } + + // Pass 2: regular map fields. + for _, f := range rpc.RequestType.MapFields { + mf := resolveMapField(f.Name, f.Type, "", tm, prefix) + highLevelParams = append(highLevelParams, HighLevelParam{ + Name: mf.Name, + ParamType: mf.ParamType, + FieldType: mf.FieldType, + }) + } + + // Pass 3: map fields from flattened messages (always with defaults). + for i, f := range rpc.RequestType.Fields { + if flatSet[i] { + msg := api.MessagesByName[f.Type] + for _, sf := range msg.MapFields { + mf := resolveMapField(sf.Name, sf.Type, "", tm, prefix) + highLevelParams = append(highLevelParams, HighLevelParam{ + Name: mf.Name, + ParamType: mf.ParamType, + FieldType: mf.FieldType, + Default: " = {}", + }) + } + } + } + + return highLevelParams, flattenedGroups +} + +// isQueryParam determines if a field should be serialized as a URL query parameter. +func isQueryParam(jsonName, bodyField string, isGet bool) bool { + if isGet || bodyField == "" { + return true + } + if bodyField == "*" { + return false + } + return jsonName != bodyField +} + +// excludeField returns a copy of fields with the named field removed. +func excludeField(fields []ResolvedField, jsonName string) []ResolvedField { + result := make([]ResolvedField, 0, len(fields)) + for _, f := range fields { + if f.JsonName != jsonName { + result = append(result, f) + } + } + return result +} + +// excludeHighLevelParam returns a copy of params with the named param removed. +func excludeHighLevelParam(params []HighLevelParam, name string) []HighLevelParam { + result := make([]HighLevelParam, 0, len(params)) + for _, p := range params { + if p.Name != name { + result = append(result, p) + } + } + return result +} diff --git a/cmd/codegen-modular/unreal/static/AsyncFuture.h b/cmd/codegen-modular/unreal/static/AsyncFuture.h new file mode 100644 index 000000000..2e7976f96 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/AsyncFuture.h @@ -0,0 +1,198 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "Tasks/Task.h" +#include "Async/TaskGraphInterfaces.h" +#include + +// Forward declaration +template struct TAsyncFuture; + +/** Type trait: is T a TAsyncFuture? */ +template struct TIsTAsyncFuture : std::false_type {}; +template struct TIsTAsyncFuture> : std::true_type {}; + +/** + * Chainable, game-thread-safe future for async operations. + * + * Parameterized by a concrete result type (e.g. FNakamaSessionResult or + * FSatoriSessionResult). Every user-visible callback is dispatched to the + * game thread via AsyncTask(ENamedThreads::GameThread, ...) so that callers + * can safely touch UObject*, fire delegates, or update UI without additional + * marshalling. + * + * Three .Next() overloads are provided: + * + * 1. Chaining with auto-propagation: + * Next(callback(const ValueType&) -> TAsyncFuture) + * Available only when ResultT defines ::ValueType. + * On error the error is forwarded to the outer future and the callback is + * skipped entirely (error propagation runs on the background thread because + * no user code is involved). + * + * 2. Chaining without auto-propagation: + * Next(callback(ResultT) -> TAsyncFuture) + * Available for all ResultT, including types without ::ValueType (e.g. + * WebSocket envelope types). The callback receives the full result and is + * responsible for inspecting bIsError and deciding what to return. + * + * 3. Terminal: + * Next(callback(ResultT) -> void) + * Called unconditionally when the future resolves. The caller inspects + * the result for errors inside the callback. + */ +template +struct TAsyncFuture +{ + using WrappedResultType = ResultT; + + struct FState + { + ResultT Result{}; + UE::Tasks::FTaskEvent Event{ UE_SOURCE_LOCATION }; + void Resolve(ResultT&& InResult) + { + Result = MoveTemp(InResult); + Event.Trigger(); + } + }; + + TSharedPtr State; + + TAsyncFuture() = default; + explicit TAsyncFuture(TSharedPtr InState) noexcept + : State(MoveTemp(InState)) {} + TAsyncFuture(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture& operator=(TAsyncFuture&& Other) noexcept = default; + TAsyncFuture(const TAsyncFuture&) = delete; + TAsyncFuture& operator=(const TAsyncFuture&) = delete; + + /** + * Overload 1 — Chaining with auto-propagation. + * callback(const ValueType&) -> TAsyncFuture + * Only enabled when ResultT has ::ValueType. + * On error, propagates to OtherResult without calling the callback. + */ + template, const VT&>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + if (CapturedState->Result.bIsError) + { + // Error propagation: no user code involved, safe on any thread. + OuterState->Resolve(InnerResultT{{}, CapturedState->Result.Error, true}); + return; + } + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(CapturedState->Result.Value); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** + * Overload 2 — Chaining, user handles errors. + * callback(ResultT) -> TAsyncFuture + * Available for all ResultT, including WebSocket types without ::ValueType. + * The callback receives the full result and is responsible for error handling. + */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + Ret Next(Func&& Callback) && noexcept + { + using InnerResultT = typename Ret::WrappedResultType; + auto OuterState = MakeShared::FState>(); + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState, OuterState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState, OuterState]() mutable + { + Ret Inner = Cb(MoveTemp(CapturedState->Result)); + auto InnerState = Inner.State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [InnerState, OuterState]() + { + OuterState->Resolve(MoveTemp(InnerState->Result)); + }, + InnerState->Event); + }); + }, + CapturedState->Event); + State.Reset(); + return TAsyncFuture(OuterState); + } + + /** Overload 3 — Terminal. callback(ResultT) -> void */ + template, ResultT>, + std::enable_if_t::value, int> = 0> + void Next(Func&& Callback) && noexcept + { + auto CapturedState = State; + UE::Tasks::Launch(UE_SOURCE_LOCATION, + [Cb = Forward(Callback), CapturedState]() mutable + { + // User callback may touch UObject* or fire delegates — dispatch to game thread. + AsyncTask(ENamedThreads::GameThread, + [Cb = MoveTemp(Cb), CapturedState]() mutable + { + Cb(MoveTemp(CapturedState->Result)); + }); + }, + CapturedState->Event); + State.Reset(); + } +}; + +/** + * Create a pre-resolved TAsyncFuture. + * Useful for returning an immediate result (e.g. a local error) from a + * chaining callback without going through the network stack. + */ +template +TAsyncFuture MakeCompletedAsyncFuture(ResultT Value) +{ + auto State = MakeShared::FState>(); + State->Resolve(MoveTemp(Value)); + return TAsyncFuture(State); +} diff --git a/cmd/codegen-modular/unreal/static/NakamaBlueprintsModule.cpp b/cmd/codegen-modular/unreal/static/NakamaBlueprintsModule.cpp new file mode 100644 index 000000000..09c24ef26 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/NakamaBlueprintsModule.cpp @@ -0,0 +1,7 @@ +#include "Modules/ModuleManager.h" + +class FNakamaBlueprintsModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FNakamaBlueprintsModule, NakamaBlueprints) diff --git a/cmd/codegen-modular/unreal/static/NakamaFuture.h b/cmd/codegen-modular/unreal/static/NakamaFuture.h new file mode 100644 index 000000000..cc373b253 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/NakamaFuture.h @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "AsyncFuture.h" + +/** + * Nakama-flavoured alias for TAsyncFuture. + * All three .Next() overloads are inherited from TAsyncFuture and dispatch + * user callbacks to the game thread. + */ +template +using TNakamaFuture = TAsyncFuture; + +/** Type trait for TNakamaFuture (delegates to TIsTAsyncFuture). */ +template using TIsTNakamaFuture = TIsTAsyncFuture; + +/** + * Create a pre-resolved TNakamaFuture. + * Thin wrapper around MakeCompletedAsyncFuture for backward compatibility. + */ +template +TNakamaFuture MakeCompletedFuture(ResultT Value) +{ + return MakeCompletedAsyncFuture(MoveTemp(Value)); +} diff --git a/cmd/codegen-modular/unreal/static/NakamaHttpHelper.h b/cmd/codegen-modular/unreal/static/NakamaHttpHelper.h new file mode 100644 index 000000000..7ad33ca22 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/NakamaHttpHelper.h @@ -0,0 +1,238 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Dom/JsonObject.h" +#include "HttpModule.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Misc/Base64.h" +#include "Misc/DateTime.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/MemoryWriter.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "NakamaTypes.h" + +/** + * Internal HTTP helpers for NakamaApi. + * All functions are typed against FNakamaClientConfig and ENakamaRequestAuth. + * + * Include this header in generated .cpp files only — not in public API headers. + */ +namespace NakamaHttpInternal +{ + +inline FString SerializeJsonToString(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(1024); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + return FString(Converter.Length(), Converter.Get()); +} + +inline FString SerializeJsonEscaped(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(2048); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + FString Condensed(Converter.Length(), Converter.Get()); + FString Escaped = Condensed.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")); + return TEXT("\"") + Escaped + TEXT("\""); +} + +inline void DoHttpRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ENakamaRequestAuth AuthType, + const FString& TokenString, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + const FString Url = Config.GetBaseUrl() + Endpoint; + + TSharedRef Request = FHttpModule::Get().CreateRequest(); + Request->SetURL(Url); + Request->SetVerb(Method); + Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + Request->SetHeader(TEXT("Accept"), TEXT("application/json")); + + switch (AuthType) + { + case ENakamaRequestAuth::Basic: + { + const FString Auth = FString::Printf(TEXT("%s:"), *Config.ServerKey); + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Basic %s"), *FBase64::Encode(Auth))); + } + break; + case ENakamaRequestAuth::Bearer: + if (!TokenString.IsEmpty()) + { + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Bearer %s"), *TokenString)); + } + break; + case ENakamaRequestAuth::HttpKey: + if (!TokenString.IsEmpty()) + { + const FString Auth = FString::Printf(TEXT("%s:"), *TokenString); + Request->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("Basic %s"), *FBase64::Encode(Auth))); + } + break; + case ENakamaRequestAuth::None: + default: + break; + } + + if (!BodyString.IsEmpty() && Method != TEXT("GET")) + { + Request->SetContentAsString(BodyString); + } + + Request->SetTimeout(Timeout); + + Request->OnProcessRequestComplete().BindLambda( + [OnSuccess, OnError, CancellationToken](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess) + { + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Request cancelled"), -1)); + } + return; + } + + if (!bSuccess || !Res.IsValid()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Connection failed"), 0)); + } + return; + } + + const int32 Code = Res->GetResponseCode(); + const FString Content = Res->GetContentAsString(); + + if (Code < 200 || Code >= 300) + { + FString ErrorMsg = FString::Printf(TEXT("HTTP %d"), Code); + int32 ErrorCode = Code; + TSharedPtr Json; + if (FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) && Json.IsValid()) + { + if (Json->HasField(TEXT("message"))) + { + ErrorMsg = Json->GetStringField(TEXT("message")); + } + if (Json->HasField(TEXT("code"))) + { + ErrorCode = static_cast(Json->GetNumberField(TEXT("code"))); + } + } + if (OnError) + { + OnError(FNakamaError(ErrorMsg, ErrorCode)); + } + return; + } + + TSharedPtr Json; + if (!Content.IsEmpty()) + { + if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(Content), Json) || !Json.IsValid()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Invalid JSON response"), 500)); + } + return; + } + } + + if (OnSuccess) + { + OnSuccess(Json); + } + }); + + Request->ProcessRequest(); +} + +inline void SendRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const FString& BodyString, + ENakamaRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + if (CancellationToken->load()) + { + if (OnError) + { + OnError(FNakamaError(TEXT("Request cancelled"), -1)); + } + return; + } + + DoHttpRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +inline void MakeRequest( + const FNakamaClientConfig& Config, + const FString& Endpoint, + const FString& Method, + const TSharedPtr& Body, + ENakamaRequestAuth AuthType, + const FString& BearerToken, + TFunction)> OnSuccess, + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString BodyString; + if (Body.IsValid() && Method != TEXT("GET")) + { + BodyString = SerializeJsonToString(Body); + } + SendRequest(Config, Endpoint, Method, BodyString, AuthType, BearerToken, OnSuccess, OnError, Timeout, CancellationToken); +} + +} // namespace NakamaHttpInternal diff --git a/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.cpp b/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.cpp new file mode 100644 index 000000000..b50f46c38 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.cpp @@ -0,0 +1,435 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NakamaWebSocketSubsystem.h" +#include "WebSocketsModule.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +DEFINE_LOG_CATEGORY(LogNakamaWebSocket); + +void UNakamaWebSocketSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + if (!FModuleManager::Get().IsModuleLoaded("WebSockets")) + { + FModuleManager::Get().LoadModule("WebSockets"); + } +} + +void UNakamaWebSocketSubsystem::Deinitialize() +{ + Close(); + + Super::Deinitialize(); +} + +void UNakamaWebSocketSubsystem::Close() +{ + StopPingLoop(); + bIsConnected = false; + + // Eagerly resolve all pending futures so their tasks don't become zombies + // that fire UE_LOG during engine teardown. + // (The async OnClosed path is skipped by the stale-socket guard when we + // call WebSocket.Reset() before the close handshake completes.) + if (ConnectionState.IsValid()) + { + auto LocalState = MoveTemp(ConnectionState); + LocalState->Resolve(FNakamaWebSocketConnectionResult{ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + { + FScopeLock Lock(&RequestsLock); + for (const auto& Request : Requests) + { + Request.Value->Resolve(FNakamaWebSocketResponse{ .ErrorCode = ENakamaWebSocketError::ConnectionClosed }); + } + Requests.Empty(); + } + + if (WebSocket.IsValid()) + { + WebSocket->Close(); + WebSocket.Reset(); + } +} + +TNakamaFuture UNakamaWebSocketSubsystem::Connect(FNakamaWebSocketConnectionParams Params) +{ + // Do we already have a connection? + if (WebSocket.IsValid()) + { + // If we have a pending connection, return failure. + if (ConnectionState.IsValid()) + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("Another WebSocket connection is in progress.")); + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionAlreadyInProgress }); + } + + // Otherwise, we have an active connection. Close it and start a new one. + UE_LOG(LogNakamaWebSocket, Warning, TEXT("Another WebSocket connection was active. Closing the old connection.")); + Close(); + } + + ConnectionParams = Params; + + // + // Construct the URL + FString Url; + if (ConnectionParams.bUseSSL) + { + Url = TEXT("wss://"); + } + else + { + Url = TEXT("ws://"); + } + + const FString TokenParam = TEXT("token=") + FGenericPlatformHttp::UrlEncode(Params.Token); + Url += ConnectionParams.Host + TEXT(":") + FString::FromInt(ConnectionParams.Port) + TEXT("/ws"); + Url += TEXT("?"); + Url += TokenParam; + + // Create the websocket + WebSocket = FWebSocketsModule::Get().CreateWebSocket(Url); + + // Use weak ptr for async safety; Pin it in the actual callback. + TWeakObjectPtr WeakThis = this; + + // + // Connectivity + WebSocket->OnConnected().AddLambda([WeakThis]() + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnConnected(); + } + }); + WebSocket->OnConnectionError().AddLambda([WeakThis, ThisSocket = WebSocket](const FString& Error) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + // Ignore stale OnConnectionError from an old socket. + if (StrongThis->WebSocket == ThisSocket) + { + StrongThis->OnConnectionError(Error); + } + } + }); + + // + // Messages + WebSocket->OnMessage().AddLambda([WeakThis](const FString& Message) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnMessage(Message); + } + }); + WebSocket->OnMessageSent().AddLambda([WeakThis](const FString& Message) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + StrongThis->OnMessageSent(Message); + } + }); + + // + // Disconnections + WebSocket->OnClosed().AddLambda([WeakThis, ThisSocket = WebSocket](int32 StatusCode, const FString& Reason, bool bWasClean) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + // Ignore stale OnClosed from an old socket. + if (StrongThis->WebSocket == ThisSocket) + { + StrongThis->OnClosed(StatusCode, Reason, bWasClean); + } + } + }); + + // + // Connect + WebSocket->Connect(); + + ConnectionState = MakeShared::FState>(); + return TNakamaFuture(ConnectionState); +} + + +void UNakamaWebSocketSubsystem::StartPingLoop() +{ + // Stop if was already running. + StopPingLoop(); + + TWeakObjectPtr WeakThis = this; + + PingTimerHandle = FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([WeakThis](float DeltaTime) + { + if (UNakamaWebSocketSubsystem* StrongThis = WeakThis.Get()) + { + return StrongThis->SendPing(); + } + return false; + }), + ConnectionParams.PingIntervalSeconds + ); +} +void UNakamaWebSocketSubsystem::StopPingLoop() +{ + if (PingTimerHandle.IsValid()) + { + FTSTicker::GetCoreTicker().RemoveTicker(PingTimerHandle); + PingTimerHandle.Reset(); + } +} +bool UNakamaWebSocketSubsystem::SendPing() +{ + FScopeLock Lock(&RequestsLock); + if (!bIsConnected) + { + return false; + } + + TSharedPtr Envelope = MakeShareable(new FJsonObject()); + Envelope->SetObjectField(TEXT("ping"), MakeShareable(new FJsonObject())); + + FString EnvelopeJson; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&EnvelopeJson); + FJsonSerializer::Serialize(Envelope.ToSharedRef(), Writer); + + WebSocket->Send(EnvelopeJson); + return true; +} + +TNakamaFuture UNakamaWebSocketSubsystem::Send(const FString& RequestName, const TSharedPtr& Data) +{ + // + // Create an Envelope, embed a new guid as Cid. + const FString Cid = FGuid::NewGuid().ToString(); + TSharedPtr Envelope = MakeShareable(new FJsonObject()); + + Envelope->SetStringField(TEXT("cid"), Cid); + Envelope->SetObjectField(RequestName, Data); + + FString EnvelopeJson; + const TSharedRef> Writer = TJsonWriterFactory<>::Create(&EnvelopeJson); + FJsonSerializer::Serialize(Envelope.ToSharedRef(), Writer); + + FScopeLock Lock(&RequestsLock); + + if (!bIsConnected) + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("WebSocket is not connected or invalid.")); + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::NotConnected }); + } + + // + // Create state, associate it with the Cid. + TSharedRef::FState> ResponseState = + MakeShared::FState>(); + + Requests.Add(Cid, ResponseState); + + // + // Send the envelope and return the future. + // Will activate it on message received. + WebSocket->Send(EnvelopeJson); + + return TNakamaFuture(ResponseState); +} + +void UNakamaWebSocketSubsystem::OnConnected() +{ + UE_LOG(LogNakamaWebSocket, Display, TEXT("WebSocket Connected.")); + + bIsConnected = true; + StartPingLoop(); + + TSharedPtr::FState> LocalState = MoveTemp(ConnectionState); + FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([LocalState](float) -> bool + { + LocalState->Resolve(FNakamaWebSocketConnectionResult{}); + return false; + }), 0.0f); +} + +void UNakamaWebSocketSubsystem::OnConnectionError(const FString& Error) +{ + UE_LOG(LogNakamaWebSocket, Warning, TEXT("WebSocket Connection Error: %s"), *Error); + + bIsConnected = false; + + TSharedPtr::FState> LocalState = MoveTemp(ConnectionState); + FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([LocalState](float) -> bool + { + LocalState->Resolve(FNakamaWebSocketConnectionResult{ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + return false; + }), 0.0f); +} + +void UNakamaWebSocketSubsystem::OnMessage(const FString& Message) +{ + // + // Try parse the message as JSON. + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Message); + if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) + { + if (MessageError.IsBound()) + { + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_MESSAGE_MALFORMED, TEXT("Failed to deserialize JSON.")); + } + return; + } + + // + // Log message (unless it's a pong). + const bool bIsPong = JsonObject->HasField(TEXT("pong")); + if (!bIsPong) + { + UE_LOG(LogNakamaWebSocket, Verbose, TEXT("WebSocket Message Received: %s"), *Message); + } + + // + // Try get the Cid from the message before anything else. + // Error responses from the server also carry a Cid. + FString Cid; + JsonObject->TryGetStringField(TEXT("cid"), Cid); + + // + // Handle possible error. + if (JsonObject->HasField(TEXT("error"))) + { + // If there is a pending request for this Cid, resolve it with an error + // so callers are not left waiting forever. + if (!Cid.IsEmpty()) + { + FScopeLock Lock(&RequestsLock); + if (auto* RequestPtr = Requests.Find(Cid)) + { + TSharedRef::FState> Request = *RequestPtr; + Request->Resolve(FNakamaWebSocketResponse{ .ErrorCode = ENakamaWebSocketError::ServerError }); + Requests.Remove(Cid); + } + } + + if (MessageError.IsBound()) + { + const TSharedPtr* ErrorObj; + FString ErrorMsg; + if (JsonObject->TryGetObjectField(TEXT("error"), ErrorObj)) + { + (*ErrorObj)->TryGetStringField(TEXT("message"), ErrorMsg); + } + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_MESSAGE_HASERROR, ErrorMsg); + } + return; + } + + // + // If we have a Cid, this is a response to a request. + if (!Cid.IsEmpty()) + { + FScopeLock Lock(&RequestsLock); + + // We should have a pending request for this CID. + if (auto* RequestPtr = Requests.Find(Cid)) + { + TSharedRef::FState> Request = *RequestPtr; + Request->Resolve(FNakamaWebSocketResponse{ .Data = JsonObject }); + + Requests.Remove(Cid); + } + else + { + UE_LOG(LogNakamaWebSocket, Warning, TEXT("No matching request for CID %s"), *Cid); + if (MessageError.IsBound()) + { + MessageError.Broadcast(EWebSocketMessageError::WS_ERROR_RESPONSE_NOCID, TEXT("No matching request for CID")); + } + } + + if (ServerResponseReceived.IsBound() && !bIsPong) + { + ServerResponseReceived.Broadcast(Message); + } + } + // Otherwise, this is a server event we need to handle. + else + { + if (ServerEventReceived.IsBound()) + { + ServerEventReceived.Broadcast(Message); + } + } +} + +void UNakamaWebSocketSubsystem::OnMessageSent(const FString& Message) +{ + UE_LOG(LogNakamaWebSocket, Verbose, TEXT("Message Sent: %s"), *Message); + + if (MessageSent.IsBound()) + { + MessageSent.Broadcast(Message); + } +} + +void UNakamaWebSocketSubsystem::OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean) +{ + bIsConnected = false; + StopPingLoop(); + + ELogVerbosity::Type Verbosity = bWasClean ? ELogVerbosity::Display : ELogVerbosity::Warning; + if (bWasClean) + { + UE_LOG( + LogNakamaWebSocket, + Display, + TEXT("WebSocket closed cleanly with status code: %d."), + StatusCode, + *Reason); + } + else + { + UE_LOG( + LogNakamaWebSocket, + Warning, + TEXT("Web Socket closed non-cleanly with status code: %d. Reason: %s."), + StatusCode, + *Reason); + } + + { + FScopeLock Lock(&RequestsLock); + + const ENakamaWebSocketError Code = bWasClean + ? ENakamaWebSocketError::None + : ENakamaWebSocketError::ConnectionClosed; + for (const auto& Request : Requests) + { + Request.Value->Resolve(FNakamaWebSocketResponse{ .ErrorCode = Code }); + } + Requests.Empty(); + } + + if (Closed.IsBound()) + { + Closed.Broadcast(StatusCode, Reason, bWasClean); + } +} + diff --git a/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.h b/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.h new file mode 100644 index 000000000..c15298af0 --- /dev/null +++ b/cmd/codegen-modular/unreal/static/NakamaWebSocketSubsystem.h @@ -0,0 +1,132 @@ +/* + * Copyright 2026 The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "IWebSocket.h" +#include "NakamaFuture.h" +#include +#include "NakamaWebSocketSubsystem.generated.h" + +NAKAMA_API DECLARE_LOG_CATEGORY_EXTERN(LogNakamaWebSocket, Log, All); + +enum class ENakamaWebSocketError : uint8 +{ + None = 0, + ConnectionAlreadyInProgress = 1, + ConnectionFailed = 2, + NotConnected = 3, + ConnectionClosed = 4, + ServerError = 5, +}; + +/** Result of a realtime send operation. Check ErrorCode before accessing Data — + * Data is nullptr when ErrorCode != ENakamaWebSocketError::None. */ +struct FNakamaWebSocketResponse +{ + ENakamaWebSocketError ErrorCode = ENakamaWebSocketError::None; + TSharedPtr Data; +}; +struct FNakamaWebSocketConnectionResult +{ + ENakamaWebSocketError ErrorCode = ENakamaWebSocketError::None; +}; +struct FNakamaWebSocketConnectionParams +{ + FString Host; + int32 Port; + FString Token; + float PingIntervalSeconds = 2.0f; + bool bUseSSL; +}; + +enum class EWebSocketMessageError : uint8 +{ + WS_ERROR_NONE = 0, + WS_ERROR_MESSAGE_MALFORMED = 1, + WS_ERROR_MESSAGE_HASERROR = 2, + WS_ERROR_RESPONSE_NOCID = 3, +}; + +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateServerResponseReceived, const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateServerEventReceived, const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FDelegateMessageSent, const FString&); +DECLARE_MULTICAST_DELEGATE_TwoParams(FDelegateMessageError, EWebSocketMessageError, const FString&); +DECLARE_MULTICAST_DELEGATE_ThreeParams(FDelegateClosed, int32, const FString&, bool); + +UCLASS() +class NAKAMA_API UNakamaWebSocketSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + // + // Implement the subsystem + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + // + // Public delegates + FDelegateServerResponseReceived ServerResponseReceived; + FDelegateServerEventReceived ServerEventReceived; + FDelegateMessageSent MessageSent; + FDelegateMessageError MessageError; + FDelegateClosed Closed; + + TNakamaFuture Connect(FNakamaWebSocketConnectionParams Params); + + TNakamaFuture Send(const FString& RequestName, const TSharedPtr& Data); + + void Close(); + + int32 GetPendingRequestCount() + { + FScopeLock Lock(&RequestsLock); + return Requests.Num(); + } + +private: + + TSharedPtr WebSocket; + + TSharedPtr::FState> ConnectionState; + std::atomic bIsConnected{false}; + + // Current ongoing requests + TMap::FState>> Requests; + FCriticalSection RequestsLock; + + // Params for current connection + FNakamaWebSocketConnectionParams ConnectionParams; + + FTSTicker::FDelegateHandle PingTimerHandle; + + // + // Ping-ponging + void StartPingLoop(); + void StopPingLoop(); + bool SendPing(); + + // + // WebSocket Callbacks + void OnConnected(); + void OnConnectionError(const FString& Error); + void OnMessage(const FString& Message); + void OnMessageSent(const FString& Message); + void OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean); +}; + diff --git a/cmd/codegen-modular/unreal/templates.go b/cmd/codegen-modular/unreal/templates.go new file mode 100644 index 000000000..210d20646 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates.go @@ -0,0 +1,9 @@ +package unreal + +import "embed" + +//go:embed templates/*.tmpl +var Templates embed.FS + +//go:embed static/* +var StaticFiles embed.FS diff --git a/cmd/codegen-modular/unreal/templates/_api-method-impl.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/_api-method-impl.ue.cpp.tmpl new file mode 100644 index 000000000..417901caf --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_api-method-impl.ue.cpp.tmpl @@ -0,0 +1,144 @@ +{{- /* + Generates all low-level API method implementations. + Receives the full ApiViewModel as dot context. + Produces the function bodies for NakamaApi::Xxx / SatoriApi::Xxx. +*/ -}} +{{- define "api_method_implementations" }} +{{- range $m := $.Methods }} + +void {{ $.Name }}Api::{{ $m.Name }}( + const {{ $.TypePrefix }}ClientConfig& Config, +{{- if $m.LeadParam }} + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }}, +{{- end }} +{{- range $m.Fields }} + {{ .ParamType }} {{ .Name }}, +{{- end }} +{{- range $m.MapFields }} + {{ .ParamType }} {{ .Name }}, +{{- end }} +{{- if $m.HasReturn }} + TFunction OnSuccess, +{{- else }} + TFunction OnSuccess, +{{- end }} + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("{{ $m.Endpoint }}"); +{{- range $m.PathParams }} + { const FString Encoded_{{ .Name }} = FGenericPlatformHttp::UrlEncode({{ .Name }}); Endpoint = Endpoint.Replace(TEXT("{{ printf "{%s}" .JsonName }}"), *Encoded_{{ .Name }}); } +{{- end }} +{{- if $m.QueryFields }} + + TArray QueryParams; +{{- range $m.QueryFields }} +{{- if .IsRepeated }} + for (const auto& Item : {{ .Name }}) + { +{{- if eq .JsonMethod "String" }} + QueryParams.Add(TEXT("{{ .JsonName }}=") + FGenericPlatformHttp::UrlEncode(Item)); +{{- else }} + QueryParams.Add(FString::Printf(TEXT("{{ .JsonName }}={{ .QueryFormat }}"), Item)); +{{- end }} + } +{{- else if eq .JsonMethod "Bool" }} + QueryParams.Add(FString::Printf(TEXT("{{ .JsonName }}=%s"), {{ .Name }} ? TEXT("true") : TEXT("false"))); +{{- else if eq .EmptyCheck "IsEmpty" }} + if (!{{ .Name }}.IsEmpty()) + { + QueryParams.Add(TEXT("{{ .JsonName }}=") + FGenericPlatformHttp::UrlEncode({{ .Name }})); + } +{{- else if eq .EmptyCheck "NonZero" }} + if ({{ .Name }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ .JsonName }}={{ .QueryFormat }}"), {{ .Name }})); + } +{{- end }} +{{- end }} + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } +{{- end }} + + {{ $.EnumPrefix }}RequestAuth AuthType = {{ $.EnumPrefix }}RequestAuth::{{ $m.AuthType }}; +{{- if $m.UseStringBody }} + + FString BodyString; + if ({{ $m.BodyFieldName }}.IsValid()) + { + BodyString = SerializeJsonEscaped({{ $m.BodyFieldName }}); + } + + SendRequest(Config, Endpoint, TEXT("{{ $m.HttpMethod }}"), BodyString, AuthType, {{ $m.AuthToken }}, +{{- else }} + + TSharedPtr Body; +{{- if eq $m.BodyMode "object" }} + Body = {{ $m.BodyFieldName }}.ToJson(); +{{- else if eq $m.BodyMode "wildcard" }} + Body = MakeShared(); +{{- range $m.WildcardFields }} +{{- if .IsRepeated }} + if ({{ .Name }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ .Name }}) + { +{{- if .IsMessage }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared<{{ .JsonArrayValue }}>(Item)); +{{- end }} + } + Body->SetArrayField(TEXT("{{ .JsonName }}"), Array); + } +{{- else if .IsMessage }} + Body->SetObjectField(TEXT("{{ .JsonName }}"), {{ .Name }}.ToJson()); +{{- else if .IsEnum }} + Body->SetNumberField(TEXT("{{ .JsonName }}"), static_cast({{ .Name }})); +{{- else if eq .EmptyCheck "IsEmpty" }} + if (!{{ .Name }}.IsEmpty()) + { + Body->Set{{ .JsonMethod }}Field(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else }} + Body->Set{{ .JsonMethod }}Field(TEXT("{{ .JsonName }}"), {{ .Name }}); +{{- end }} +{{- end }} +{{- range $m.WildcardMapFields }} + if ({{ .Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ .Name }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("{{ .JsonName }}"), MapObj); + } +{{- end }} +{{- end }} + + MakeRequest(Config, Endpoint, TEXT("{{ $m.HttpMethod }}"), Body, AuthType, {{ $m.AuthToken }}, +{{- end }} + [OnSuccess](TSharedPtr Json) + { +{{- if $m.HasReturn }} + {{ $.TypePrefix }}{{ $m.ReturnType }} Result = {{ $.TypePrefix }}{{ $m.ReturnType }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } +{{- else }} + if (OnSuccess) + { + OnSuccess(); + } +{{- end }} + }, + OnError, Timeout, CancellationToken); +} +{{- end }} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_client-bplib.ue.tmpl b/cmd/codegen-modular/unreal/templates/_client-bplib.ue.tmpl new file mode 100644 index 000000000..75954127c --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_client-bplib.ue.tmpl @@ -0,0 +1,190 @@ +{{- /* + Shared Blueprint delegate declarations. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "bp_delegates" }} + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOn{{ $.Name }}Error, const {{ $.TypePrefix }}Error&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOn{{ $.Name }}Success); + +{{- /* Generate success delegates for each unique return type */ -}} +{{- range $.UniqueReturnTypes }} +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOn{{ $.Name }}{{ . }}, const {{ $.TypePrefix }}{{ . }}&, Result); +{{- end }} +{{- end }} + +{{- /* + Shared Blueprint async action class declarations. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "bp_action_class_declarations" }} + +// ============================================================================ +// Async Action Classes (one per RPC) +// ============================================================================ +{{- range $m := $.Methods }} + +/** + * {{ $m.Comment }} + */ +UCLASS(Transient) +class {{ $.BpExport }} U{{ $.Name }}Client{{ $m.Name }} : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: +{{- if $m.HasReturn }} + UPROPERTY(BlueprintAssignable) + FOn{{ $.Name }}{{ $m.ReturnType }} OnSuccess; +{{- else }} + UPROPERTY(BlueprintAssignable) + FOn{{ $.Name }}Success OnSuccess; +{{- end }} + + UPROPERTY(BlueprintAssignable) + FOn{{ $.Name }}Error OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "{{ $m.Name }}"), Category = "{{ $.Name }}|Client") + static U{{ $.Name }}Client{{ $m.Name }}* {{ $m.Name }}( + UObject* WorldContextObject, + {{ $.TypePrefix }}ClientConfig Client +{{- if $m.LeadParam }}, + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }} +{{- end }} +{{- range $m.HighLevelParams }}, + {{ .ParamType }} {{ .Name }} +{{- end }}); + + virtual void Activate() override; + +private: + {{ $.TypePrefix }}ClientConfig Client; + +{{- if $m.LeadParam }} +{{- if eq $m.AuthType "Bearer" }} + {{ $.TypePrefix }}Session Session; +{{- end }} +{{- end }} +{{- range $m.HighLevelParams }} +{{- if .IsScalarBody }} + TMap Stored{{ .Name }}; +{{- else }} + {{ .FieldType }} Stored{{ .Name }}{{ .FieldDefault }}; +{{- end }} +{{- end }} +}; +{{- end }} +{{- end }} + +{{- /* + Shared Blueprint async action class implementations. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "bp_action_class_implementations" }} + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ +{{- range $m := $.Methods }} + +// {{ $m.Name }} +U{{ $.Name }}Client{{ $m.Name }}* U{{ $.Name }}Client{{ $m.Name }}::{{ $m.Name }}( + UObject* WorldContextObject, + {{ $.TypePrefix }}ClientConfig Client +{{- if $m.LeadParam }}, + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }} +{{- end }} +{{- range $m.HighLevelParams }}, + {{ .ParamType }} {{ .Name }} +{{- end }}) +{ + U{{ $.Name }}Client{{ $m.Name }}* Action = NewObject(GetTransientPackage()); + Action->Client = Client; +{{- if $m.LeadParam }} +{{- if eq $m.AuthType "Bearer" }} + Action->Session = Session; +{{- end }} +{{- end }} +{{- range $m.HighLevelParams }} + Action->Stored{{ .Name }} = {{ .Name }}; +{{- end }} + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void U{{ $.Name }}Client{{ $m.Name }}::Activate() +{ + static const TCHAR* TraceScope_{{ $m.Name }} = TEXT("{{ $.Name }}BP_{{ $m.Name }}"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_{{ $m.Name }}); + + TWeakObjectPtr WeakThis(this); + + {{- range $g := $m.FlattenedGroups }} + {{ $g.FieldType }} Stored{{ $g.FieldName }}; + {{- range $g.SubFields }} + Stored{{ $g.FieldName }}.{{ . }} = Stored{{ . }}; + {{- end }} + {{- end }} + + {{ $.Name }}Api::{{ $m.Name }}( + Client, +{{- if $m.LeadParam }} +{{- if eq $m.AuthType "Bearer" }} + Session, +{{- else if eq $m.AuthType "HttpKey" }} + Stored{{ $m.LeadParam.Name }}, +{{- end }} +{{- end }} +{{- range $m.Fields }} +{{- if and $m.UseStringBody (eq .Name $m.BodyFieldName) }} + [&]() -> TSharedPtr { + if (Stored{{ .Name }}.Num() == 0) { return nullptr; } + TSharedPtr Json = MakeShared(); + for (const auto& Pair : Stored{{ .Name }}) + { + Json->SetStringField(Pair.Key, Pair.Value); + } + return Json; + }(), +{{- else }} + Stored{{ .Name }}, +{{- end }} +{{- end }} +{{- range $m.MapFields }} + Stored{{ .Name }}, +{{- end }} +{{- if $m.HasReturn }} + [WeakThis](const {{ $.TypePrefix }}{{ $m.ReturnType }}& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, +{{- else }} + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, +{{- end }} + [WeakThis](const {{ $.TypePrefix }}Error& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} +{{- end }} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_errorcodes.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/_errorcodes.ue.h.tmpl new file mode 100644 index 000000000..dcfe0787b --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_errorcodes.ue.h.tmpl @@ -0,0 +1,23 @@ +{{- define "errorcodes" }} +/** gRPC status codes returned by {{ . }} in F{{ . }}Error::Code. */ +namespace E{{ . }}ErrorCode +{ + constexpr int32 OK = 0; + constexpr int32 Cancelled = 1; + constexpr int32 Unknown = 2; + constexpr int32 InvalidArgument = 3; + constexpr int32 DeadlineExceeded = 4; + constexpr int32 NotFound = 5; + constexpr int32 AlreadyExists = 6; + constexpr int32 PermissionDenied = 7; + constexpr int32 ResourceExhausted = 8; + constexpr int32 FailedPrecondition = 9; + constexpr int32 Aborted = 10; + constexpr int32 OutOfRange = 11; + constexpr int32 Unimplemented = 12; + constexpr int32 Internal = 13; + constexpr int32 Unavailable = 14; + constexpr int32 DataLoss = 15; + constexpr int32 Unauthenticated = 16; +} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_fromjson-tojson.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/_fromjson-tojson.ue.cpp.tmpl new file mode 100644 index 000000000..79260c4c0 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_fromjson-tojson.ue.cpp.tmpl @@ -0,0 +1,165 @@ +{{- /* + Shared FromJson field-reading logic for a single message. + Receives a codegen.ResolvedMessage as dot context. + All type references (.MessageType, .EnumType, etc.) are pre-resolved + by the Go code and already contain the correct prefix. +*/ -}} +{{- define "fromjson_read_fields" }} +{{- range .Fields }} +{{- if .IsRepeated }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ .JsonName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if .IsMessage }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ .Name }}.Add({{ .MessageType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ .Name }}.Add({{ .ArrayItemExpr }}); +{{- end }} + } + } + } +{{- else if .IsMessage }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ .JsonName }}"), NestedObj)) + { + Result.{{ .Name }} = {{ .MessageType }}::FromJson(*NestedObj); + } + } +{{- else if .IsEnum }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = static_cast<{{ .EnumType }}>(Json->GetIntegerField(TEXT("{{ .JsonName }}"))); + } +{{- else if ne .CastFromJson "" }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = {{ .CastFromJson }}(Json->{{ .JsonGetter }}(TEXT("{{ .JsonName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = Json->{{ .JsonGetter }}(TEXT("{{ .JsonName }}")); + } +{{- end }} +{{- end }} +{{- range .MapFields }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ .JsonName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ .Name }}.Add(Pair.Key, {{ .MapValueReader }}); + } + } + } +{{- end }} +{{- end }} + +{{- /* + Shared ToJson field-writing logic for a single message. + Receives a codegen.ResolvedMessage as dot context. +*/ -}} +{{- define "tojson_write_fields" }} +{{- range .Fields }} +{{- if .IsRepeated }} + if ({{ .Name }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ .Name }}) + { +{{- if .IsMessage }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared<{{ .JsonArrayValue }}>(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ .JsonName }}"), Array); + } +{{- else if .IsMessage }} + Json->SetObjectField(TEXT("{{ .JsonName }}"), {{ .Name }}.ToJson()); +{{- else if .IsEnum }} + Json->SetNumberField(TEXT("{{ .JsonName }}"), static_cast({{ .Name }})); +{{- else if eq .ToJsonGuard "IsEmpty" }} + if (!{{ .Name }}.IsEmpty()) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else if eq .ToJsonGuard "NonZero" }} + if ({{ .Name }} != 0) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else }} + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); +{{- end }} +{{- end }} +{{- range .MapFields }} + if ({{ .Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ .Name }}) + { + MapObj->{{ .MapValueSetter }}(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ .JsonName }}"), MapObj); + } +{{- end }} +{{- end }} + +{{- /* + Generates all FromJson + ToJson implementations for every message. + Receives the full ApiViewModel as dot context. + Handles both Session (with ParseTokens) and non-session messages. +*/ -}} +{{- define "all_fromjson_tojson" }} +{{- range $msg := $.Messages }} + +{{ $.TypePrefix }}{{ $msg.Name }} {{ $.TypePrefix }}{{ $msg.Name }}::FromJson(const TSharedPtr& Json) noexcept +{ + {{ $.TypePrefix }}{{ $msg.Name }} Result; + if (!Json.IsValid()) + { + return Result; + } +{{- template "fromjson_read_fields" $msg }} +{{- if $msg.IsSession }} + Result.ParseTokens(); +{{- end }} + return Result; +} + +TSharedPtr {{ $.TypePrefix }}{{ $msg.Name }}::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- template "tojson_write_fields" $msg }} + return Json; +} +{{- end }} +{{- end }} + +{{- /* + Generates the ClientConfig::GetBaseUrl implementation. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "clientconfig_getbaseurl" }} + +// --- {{ $.TypePrefix }}ClientConfig --- + +FString {{ $.TypePrefix }}ClientConfig::GetBaseUrl() const noexcept +{ + const FString Scheme = bUseSSL ? TEXT("https") : TEXT("http"); + return FString::Printf(TEXT("%s://%s:%d"), *Scheme, *Host, Port); +} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_product-nakama.ue.tmpl b/cmd/codegen-modular/unreal/templates/_product-nakama.ue.tmpl new file mode 100644 index 000000000..f52404d4f --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_product-nakama.ue.tmpl @@ -0,0 +1,252 @@ +{{- /* + Nakama-specific template defines. + Loaded as a partial only for Nakama production groups. +*/ -}} + +{{- define "session_struct_decl" }} +{{- range $msg := $.Messages }} +{{- if $msg.IsSession }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct {{ $.ApiExport }} {{ $.TypePrefix }}Session +{ + GENERATED_BODY() + + {{- range $msg.Fields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}{{ .FieldDefault }}; + {{- end }} + {{- range $msg.MapFields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}; + {{- end }} + + /** User ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + FString UserId; + + /** Username parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + FString Username; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 RefreshTokenExpiresAt = 0; + + /** Session variables from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + TMap Vars; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static {{ $.TypePrefix }}Session FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; +{{- end }} +{{- end }} +{{- end }} + +{{- define "auth_enum_decl" }} + +enum class {{ $.EnumPrefix }}RequestAuth : uint8 +{ + None, + Basic, + Bearer, + HttpKey +}; +{{- end }} + +{{- define "session_jwt_helpers" }} + +// --- {{ $.TypePrefix }}Session JWT helpers --- + +bool {{ $.TypePrefix }}Session::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void {{ $.TypePrefix }}Session::ParseTokens() noexcept +{ + UserId.Empty(); + Username.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + Vars.Empty(); + + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("uid"))) + { + UserId = TokenPayload->GetStringField(TEXT("uid")); + } + if (TokenPayload->HasField(TEXT("usn"))) + { + Username = TokenPayload->GetStringField(TEXT("usn")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + if (TokenPayload->HasField(TEXT("vrs"))) + { + const TSharedPtr* VrsObj; + if (TokenPayload->TryGetObjectField(TEXT("vrs"), VrsObj)) + { + for (const auto& Pair : (*VrsObj)->Values) + { + Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + } + + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool {{ $.TypePrefix }}Session::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool {{ $.TypePrefix }}Session::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void {{ $.TypePrefix }}Session::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} +{{- end }} + +{{- define "hl_api_extra_includes" }} +#include "{{ $.Name }}Future.h" +{{- end }} + +{{- define "hl_api_future_aliases" }} +{{- end }} + +{{- define "maybe_refresh_then_call" }} + +namespace +{ + +void MaybeRefreshThenCall( + const TSharedRef<{{ $.TypePrefix }}Session>& SessionState, + const {{ $.TypePrefix }}ClientConfig& ClientConfig, + const {{ $.TypePrefix }}RetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)({{ $.TypePrefix }}Error(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + {{ $.Name }}Api::SessionRefresh( + ClientConfig, + SessionState->RefreshToken, + {}, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const {{ $.TypePrefix }}Session& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const {{ $.TypePrefix }}Error& Error) + { + (*OnError)({{ $.TypePrefix }}Error(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_product-satori.ue.tmpl b/cmd/codegen-modular/unreal/templates/_product-satori.ue.tmpl new file mode 100644 index 000000000..698e4c23e --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_product-satori.ue.tmpl @@ -0,0 +1,237 @@ +{{- /* + Satori-specific template defines. + Loaded as a partial only for Satori production groups. +*/ -}} + +{{- define "session_struct_decl" }} +{{- range $msg := $.Messages }} +{{- if $msg.IsSession }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct {{ $.ApiExport }} {{ $.TypePrefix }}Session +{ + GENERATED_BODY() + + {{- range $msg.Fields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}{{ .FieldDefault }}; + {{- end }} + {{- range $msg.MapFields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}; + {{- end }} + + /** Identity ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + FString IdentityId; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "{{ $.Name }}") + int64 RefreshTokenExpiresAt = 0; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static {{ $.TypePrefix }}Session FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; +{{- end }} +{{- end }} +{{- end }} + +{{- define "auth_enum_decl" }} + +enum class {{ $.EnumPrefix }}RequestAuth : uint8 +{ + None, + Basic, + Bearer +}; +{{- end }} + +{{- define "session_jwt_helpers" }} + +// --- {{ $.TypePrefix }}Session JWT helpers --- + +bool {{ $.TypePrefix }}Session::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void {{ $.TypePrefix }}Session::ParseTokens() noexcept +{ + IdentityId.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("iid"))) + { + IdentityId = TokenPayload->GetStringField(TEXT("iid")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + } + + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool {{ $.TypePrefix }}Session::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool {{ $.TypePrefix }}Session::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void {{ $.TypePrefix }}Session::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} +{{- end }} + +{{- define "hl_api_extra_includes" }} +#include "AsyncFuture.h" +{{- end }} + +{{- define "hl_api_future_aliases" }} + +/** + * Satori-flavoured alias for TAsyncFuture. + * All three .Next() overloads are inherited from TAsyncFuture and dispatch + * user callbacks to the game thread, making it safe to touch UObject*, + * fire delegates, or update UI from any .Next() callback. + */ +template +using T{{ $.Name }}Future = TAsyncFuture; + +/** Type trait for T{{ $.Name }}Future (delegates to TIsTAsyncFuture). */ +template using TIsT{{ $.Name }}Future = TIsTAsyncFuture; +{{- end }} + +{{- define "maybe_refresh_then_call" }} + +namespace +{ + +void MaybeRefreshThenCall( + const TSharedRef<{{ $.TypePrefix }}Session>& SessionState, + const {{ $.TypePrefix }}ClientConfig& ClientConfig, + const {{ $.TypePrefix }}RetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)({{ $.TypePrefix }}Error(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + {{ $.Name }}Api::AuthenticateRefresh( + ClientConfig, + SessionState->RefreshToken, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const {{ $.TypePrefix }}Session& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const {{ $.TypePrefix }}Error& Error) + { + (*OnError)({{ $.TypePrefix }}Error(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_serializejson.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/_serializejson.ue.cpp.tmpl new file mode 100644 index 000000000..6394d6d09 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_serializejson.ue.cpp.tmpl @@ -0,0 +1,29 @@ +{{- define "serializejson" }} +FString SerializeJsonToString(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(1024); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + return FString(Converter.Length(), Converter.Get()); +} + +FString SerializeJsonEscaped(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(2048); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + FString Condensed(Converter.Length(), Converter.Get()); + FString Escaped = Condensed.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")); + return TEXT("\"") + Escaped + TEXT("\""); +} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/_types-decl.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/_types-decl.ue.h.tmpl new file mode 100644 index 000000000..c14fbd4ca --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/_types-decl.ue.h.tmpl @@ -0,0 +1,84 @@ +{{- /* + Shared forward declarations for all messages. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "forward_declarations" }} +// Forward declarations +{{- range $.Messages }} +struct {{ $.TypePrefix }}{{ .Name }}; +{{- end }} +{{- end }} + +{{- /* + Shared enum declarations. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "enum_declarations" }} +{{- range $enum := $.Enums }} + +/** {{ $enum.Comment }} */ +UENUM(BlueprintType) +enum class {{ $.EnumPrefix }}{{ $enum.Name }} : uint8 +{ + {{- range $enum.Fields }} + {{ .Name }} = {{ .Value }}{{ if not .IsLast }},{{ end }} + {{- end }} +}; +{{- end }} +{{- end }} + +{{- /* + Shared error struct declaration. + Receives the full ApiViewModel as dot context. +*/ -}} +{{- define "error_struct" }} + +USTRUCT(BlueprintType) +struct {{ $.ApiExport }} {{ $.TypePrefix }}Error +{ + GENERATED_BODY() + + {{ $.TypePrefix }}Error() = default; + {{ $.TypePrefix }}Error(const FString& InMessage, int32 InCode) : Message(InMessage), Code(InCode) {} + + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + FString Message; + + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + int32 Code = 0; +}; +{{- end }} + +{{- /* + Shared non-session struct declarations. + Receives the full ApiViewModel as dot context. + Session structs are excluded because they have hand-written + members that differ between products (JWT-parsed fields). +*/ -}} +{{- define "non_session_struct_declarations" }} +{{- range $msg := $.Messages }} +{{- if not $msg.IsSession }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct {{ $.ApiExport }} {{ $.TypePrefix }}{{ $msg.Name }} +{ + GENERATED_BODY() + + {{- range $msg.Fields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}{{ .FieldDefault }}; + {{- end }} + {{- range $msg.MapFields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "{{ $.Name }}") + {{ .FieldType }} {{ .Name }}; + {{- end }} + + static {{ $.TypePrefix }}{{ $msg.Name }} FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; +{{- end }} +{{- end }} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/api-decl.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/api-decl.ue.h.tmpl new file mode 100644 index 000000000..9002de46e --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/api-decl.ue.h.tmpl @@ -0,0 +1,56 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" +#include "{{ $.Name }}Types.h" + +{{ $.ApiExport }} DECLARE_LOG_CATEGORY_EXTERN(Log{{ $.Name }}, Log, All); + +/** Low-level {{ $.Name }} API: free functions for HTTP RPCs (callback-based). */ +namespace {{ $.Name }}Api +{ +{{- range $m := $.Methods }} + + /** {{ $m.Comment }} */ + {{ $.ApiExport }} void {{ $m.Name }}( + const {{ $.TypePrefix }}ClientConfig& Config, + {{- if $m.LeadParam }} + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }}, + {{- end }} + {{- range $m.Fields }} + {{ .ParamType }} {{ .Name }}, + {{- end }} + {{- range $m.MapFields }} + {{ .ParamType }} {{ .Name }}, + {{- end }} + {{- if $m.HasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +} diff --git a/cmd/codegen-modular/unreal/templates/api-impl.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/api-impl.ue.cpp.tmpl new file mode 100644 index 000000000..b9f7eb570 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/api-impl.ue.cpp.tmpl @@ -0,0 +1,46 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "{{ $.Name }}Api.h" +#include "{{ $.Name }}HttpHelper.h" + +#include "Modules/ModuleManager.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +DEFINE_LOG_CATEGORY(Log{{ $.Name }}); + +using namespace {{ $.Name }}HttpInternal; + +{{- template "api_method_implementations" . }} + +// Module implementation +class F{{ $.Name }}ApiModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(Log{{ $.Name }}, Log, TEXT("{{ $.Name }}Api module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(Log{{ $.Name }}, Log, TEXT("{{ $.Name }}Api module shutting down")); + } +}; + +IMPLEMENT_MODULE(F{{ $.Name }}ApiModule, {{ $.Name }}Api) diff --git a/Satori/Source/SatoriCore/Public/satori-cpp/SExport.h b/cmd/codegen-modular/unreal/templates/client-bplib.ue.cpp.tmpl similarity index 74% rename from Satori/Source/SatoriCore/Public/satori-cpp/SExport.h rename to cmd/codegen-modular/unreal/templates/client-bplib.ue.cpp.tmpl index 97acef21e..796653cb7 100644 --- a/Satori/Source/SatoriCore/Public/satori-cpp/SExport.h +++ b/cmd/codegen-modular/unreal/templates/client-bplib.ue.cpp.tmpl @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Nakama Authors + * Copyright {{currentYear}} The Nakama Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -#pragma once +/* This code is auto-generated. DO NOT EDIT. */ -#undef SATORI_API +#include "{{ $.Name }}ClientBlueprintLibrary.h" -#define SATORI_API +{{- template "bp_action_class_implementations" . }} diff --git a/Nakama/Source/NakamaBlueprints/Public/NakamaBlueprints.h b/cmd/codegen-modular/unreal/templates/client-bplib.ue.h.tmpl similarity index 61% rename from Nakama/Source/NakamaBlueprints/Public/NakamaBlueprints.h rename to cmd/codegen-modular/unreal/templates/client-bplib.ue.h.tmpl index db69caea8..fca7de1ea 100644 --- a/Nakama/Source/NakamaBlueprints/Public/NakamaBlueprints.h +++ b/cmd/codegen-modular/unreal/templates/client-bplib.ue.h.tmpl @@ -1,5 +1,5 @@ /* -* Copyright 2025 The Nakama Authors + * Copyright {{currentYear}} The Nakama Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,17 @@ * limitations under the License. */ +/* This code is auto-generated. DO NOT EDIT. */ + #pragma once -//#include "NakamaUnreal.h" -//#include "nonstd/optional.hpp" #include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogNakamaBlueprints, Log, All); - - -class FNakamaBlueprintsModule : public IModuleInterface -{ -public: +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "{{ $.Name }}Types.h" +#include "{{ $.Name }}Api.h" - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; +#include "{{ $.Name }}ClientBlueprintLibrary.generated.h" -}; +{{- template "bp_delegates" . }} +{{- template "bp_action_class_declarations" . }} diff --git a/cmd/codegen-modular/unreal/templates/hl-api.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/hl-api.ue.cpp.tmpl new file mode 100644 index 000000000..1364e7a5a --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/hl-api.ue.cpp.tmpl @@ -0,0 +1,187 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "{{ $.Name }}.h" +#include "Containers/Ticker.h" +#include "Modules/ModuleManager.h" + +bool {{ $.Name }}::IsTransientError(const {{ $.TypePrefix }}Error& Error) noexcept +{ + switch (Error.Code) + { + case 0: // Connection failed (TCP refused, DNS timeout, etc.) + case 4: // DEADLINE_EXCEEDED + case 13: // INTERNAL + case 14: // UNAVAILABLE + return true; + default: + return false; + } +} + +float {{ $.Name }}::CalculateBackoff(int32 Attempt, const {{ $.TypePrefix }}RetryConfig& Config) noexcept +{ + const float ExponentialDelay = FMath::Pow(2.0f, static_cast(Attempt - 1)) * Config.BaseDelayMs; + const float Jitter = FMath::FRand() * ExponentialDelay; + return Jitter / 1000.0f; +} + +{{- template "maybe_refresh_then_call" . }} +{{- range $m := $.Methods }} + +T{{ $.Name }}Future<{{ $m.AsyncResultType }}Result> {{ $.Name }}::{{ $m.Name }}( + const {{ $.TypePrefix }}ClientConfig& ClientConfig, + {{- if $m.LeadParam }} + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }}, + {{- end }} + {{- range $m.HighLevelParams }} + {{ .ParamType }} {{ .Name }}, + {{- end }} + const {{ $.TypePrefix }}RetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); +{{- if eq $m.AuthType "Bearer" }} + auto SessionState = MakeShared<{{ $.TypePrefix }}Session>(Session); +{{- end }} + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const {{ $.TypePrefix }}Error& Error) + { + if ({{ $.Name }}::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = {{ $.Name }}::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $m.AsyncResultType }}Result{{ "{{" }}}, Error, true}); + } + }; + + *DoRequest = [FutureState, {{- if eq $m.AuthType "Bearer" }}SessionState, {{- end }}DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- if eq $m.AuthType "HttpKey" }}, HttpKey{{- end }} + {{- range $m.HighLevelParams }}, {{ .Name }}{{- end }}]() + { +{{- if eq $m.AuthType "Bearer" }} + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- range $m.HighLevelParams }}, {{ .Name }}{{- end }}]() + { + {{- range $g := $m.FlattenedGroups }} + {{ $g.FieldType }} {{ $g.FieldName }}; + {{- range $g.SubFields }} + {{ $g.FieldName }}.{{ . }} = {{ . }}; + {{- end }} + {{- end }} + {{ $.Name }}Api::{{ $m.Name }}( + ClientConfig, + *SessionState, + {{- range $m.Fields }} + {{ .Name }}, + {{- end }} + {{- range $m.MapFields }} + {{ .Name }}, + {{- end }} + {{- if $m.HasReturn }} + [FutureState, DoRequest, OnError](const {{ $.TypePrefix }}{{ $m.ReturnType }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $m.AsyncResultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $.TypePrefix }}VoidResult{{ "{" }}{{ $.TypePrefix }}Void{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); +{{- else }} + {{- range $g := $m.FlattenedGroups }} + {{ $g.FieldType }} {{ $g.FieldName }}; + {{- range $g.SubFields }} + {{ $g.FieldName }}.{{ . }} = {{ . }}; + {{- end }} + {{- end }} + {{ $.Name }}Api::{{ $m.Name }}( + ClientConfig, + {{- if eq $m.AuthType "HttpKey" }} + HttpKey, + {{- end }} + {{- range $m.Fields }} + {{ .Name }}, + {{- end }} + {{- range $m.MapFields }} + {{ .Name }}, + {{- end }} + {{- if $m.HasReturn }} + [FutureState, DoRequest, OnError](const {{ $.TypePrefix }}{{ $m.ReturnType }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $m.AsyncResultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $.TypePrefix }}VoidResult{{ "{" }}{{ $.TypePrefix }}Void{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); +{{- end }} + }; + + (*DoRequest)(); + + return T{{ $.Name }}Future<{{ $m.AsyncResultType }}Result>(FutureState); +} +{{- end }} + +// Module implementation +class F{{ $.Name }}Module : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(Log{{ $.Name }}, Log, TEXT("{{ $.Name }} module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(Log{{ $.Name }}, Log, TEXT("{{ $.Name }} module shutting down")); + } +}; + +IMPLEMENT_MODULE(F{{ $.Name }}Module, {{ $.Name }}) diff --git a/cmd/codegen-modular/unreal/templates/hl-api.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/hl-api.ue.h.tmpl new file mode 100644 index 000000000..4f916db94 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/hl-api.ue.h.tmpl @@ -0,0 +1,107 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "{{ $.Name }}Api.h" +{{- template "hl_api_extra_includes" . }} + +/** Tag type used as the value type for RPCs that return no data. */ +struct {{ $.HlExport }} {{ $.TypePrefix }}Void {}; + +struct {{ $.HlExport }} {{ $.TypePrefix }}VoidResult +{ + using ValueType = {{ $.TypePrefix }}Void; + {{ $.TypePrefix }}Void Value{}; + {{ $.TypePrefix }}Error Error; + bool bIsError = true; +}; +{{- range $.UniqueReturnTypes }} + +struct {{ $.HlExport }} {{ $.TypePrefix }}{{ . }}Result +{ + using ValueType = {{ $.TypePrefix }}{{ . }}; + {{ $.TypePrefix }}{{ . }} Value{}; + {{ $.TypePrefix }}Error Error; + bool bIsError = true; +}; +{{- end }} + +{{- template "hl_api_future_aliases" . }} + +/** Retry configuration for transient error handling + session auto-refresh. */ +struct {{ $.HlExport }} {{ $.TypePrefix }}RetryConfig +{ + /** Maximum number of retry attempts (0 = no retries). */ + int32 MaxRetries = 4; + + /** Base delay in milliseconds before first retry. Actual delay uses exponential backoff with full jitter. */ + int32 BaseDelayMs = 500; + + /** Whether to automatically refresh expired sessions before making API calls. */ + bool bAutoRefreshSession = true; + + /** How many seconds before expiry a session is considered "about to expire" and gets refreshed. */ + int64 AutoRefreshBufferSeconds = 300; + + /** HTTP request timeout in seconds. */ + float Timeout = 10.0f; + + /** + * Called after a session is automatically refreshed, so callers can persist the updated session. + * + * If the callback captures a UObject pointer, set OnSessionRefreshedOwner to that object. + * The callback will be skipped if the owner has been garbage-collected, preventing + * dangling-pointer crashes during in-flight retries. + */ + TFunction OnSessionRefreshed; + + /** Optional weak owner for OnSessionRefreshed. When set, the callback is skipped if the owner is stale. */ + TWeakObjectPtr OnSessionRefreshedOwner; +}; + +/** + * High-level {{ $.Name }} API: free functions with retry logic + session auto-refresh. + * + * Transient errors (UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED) are + * automatically retried with exponential backoff and full jitter. + */ +namespace {{ $.Name }} +{ + /** Returns true if the error code is considered transient (eligible for retry). */ + {{ $.HlExport }} bool IsTransientError(const {{ $.TypePrefix }}Error& Error) noexcept; + + /** Compute backoff delay in seconds for a given attempt using exponential backoff with full jitter. */ + {{ $.HlExport }} float CalculateBackoff(int32 Attempt, const {{ $.TypePrefix }}RetryConfig& Config) noexcept; + +{{- range $m := $.Methods }} + + /** {{ $m.Comment }} */ + {{ $.HlExport }} T{{ $.Name }}Future<{{ $m.AsyncResultType }}Result> {{ $m.Name }}( + const {{ $.TypePrefix }}ClientConfig& ClientConfig, + {{- if $m.LeadParam }} + {{ $m.LeadParam.Type }} {{ $m.LeadParam.Name }}, + {{- end }} + {{- range $m.HighLevelParams }} + {{ .ParamType }} {{ .Name }}{{ .Default }}, + {{- end }} + const {{ $.TypePrefix }}RetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.cpp.tmpl new file mode 100644 index 000000000..0ac32405a --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.cpp.tmpl @@ -0,0 +1,142 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ +{{- range $op := $.RtOperations }} + +// {{ $op.MessageName }} +UNakamaRealtimeClient{{ $op.MessageName }}* UNakamaRealtimeClient{{ $op.MessageName }}::{{ $op.MessageName }}( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem +{{- range $op.Fields }}, + {{ .ParamType }} {{ .Name }} +{{- end }} +{{- range $op.MapFields }}, + const {{ .FieldType }}& {{ .Name }} +{{- end }}) +{ + UNakamaRealtimeClient{{ $op.MessageName }}* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; +{{- range $op.Fields }} + Action->Stored{{ .Name }} = {{ .Name }}; +{{- end }} +{{- range $op.MapFields }} + Action->Stored{{ .Name }} = {{ .Name }}; +{{- end }} + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClient{{ $op.MessageName }}::Activate() +{ + static const TCHAR* TraceScope_{{ $op.MessageName }} = TEXT("NakamaRTBP_{{ $op.MessageName }}"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_{{ $op.MessageName }}); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); +{{- range $op.Fields }} +{{- if .IsRepeated }} + if (Stored{{ .Name }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : Stored{{ .Name }}) + { +{{- if .IsMessage }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared<{{ .JsonArrayValue }}>(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ .JsonName }}"), Array); + } +{{- else if .IsBytes }} + Json->SetStringField(TEXT("{{ .JsonName }}"), FBase64::Encode(Stored{{ .Name }})); +{{- else if .IsMessage }} + Json->SetObjectField(TEXT("{{ .JsonName }}"), Stored{{ .Name }}.ToJson()); +{{- else if .IsEnum }} + Json->SetNumberField(TEXT("{{ .JsonName }}"), static_cast(Stored{{ .Name }})); +{{- else if eq .ToJsonGuard "IsEmpty" }} + if (!Stored{{ .Name }}.IsEmpty()) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), Stored{{ .Name }}); + } +{{- else if eq .ToJsonGuard "NonZero" }} + if (Stored{{ .Name }} != 0) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), Stored{{ .Name }}); + } +{{- else }} + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), Stored{{ .Name }}); +{{- end }} +{{- end }} +{{- range $op.MapFields }} + if (Stored{{ .Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Stored{{ .Name }}) + { + MapObj->{{ .MapValueSetter }}(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ .JsonName }}"), MapObj); + } +{{- end }} + + StoredWebSocketSubsystem->Send(TEXT("{{ $op.CaseName }}"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.h.tmpl new file mode 100644 index 000000000..cda37da55 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rtclient-bplib.ue.h.tmpl @@ -0,0 +1,78 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "NakamaRtTypes.h" +#include "NakamaWebSocketSubsystem.h" + +#include "NakamaRtClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaRtError, const FNakamaRtError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNakamaRtSuccess); + +// ============================================================================ +// Async Action Classes (one per RT operation) +// ============================================================================ +{{- range $op := $.RtOperations }} + +/** + * {{ $op.Comment }} + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClient{{ $op.MessageName }} : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "{{ $op.MessageName }}"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClient{{ $op.MessageName }}* {{ $op.MessageName }}( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem +{{- range $op.Fields }}, + {{ .ParamType }} {{ .Name }} +{{- end }} +{{- range $op.MapFields }}, + const {{ .FieldType }}& {{ .Name }} +{{- end }}); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; +{{- range $op.Fields }} + {{ .FieldType }} Stored{{ .Name }}{{ .FieldDefault }}; +{{- end }} +{{- range $op.MapFields }} + {{ .FieldType }} Stored{{ .Name }}; +{{- end }} +}; +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.cpp.tmpl new file mode 100644 index 000000000..dcc81affc --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.cpp.tmpl @@ -0,0 +1,109 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRt.h" +#include "NakamaApi.h" + +namespace Nakama +{ +TNakamaFuture NakamaRealtimeClient::Connect( + const FNakamaWebSocketConnectionParams& Params +) noexcept +{ + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Connect(Params); +} +{{- range $op := $.RtOperations }} + +TNakamaFuture NakamaRealtimeClient::{{ $op.MessageName }}( +{{- range $op.OneofFields }} + {{ .ParamType }} {{ .Name }}, +{{- end }} +{{- range $op.Fields }} + {{ .ParamType }} {{ .Name }}, +{{- end }} +{{- range $op.MapFields }} + const {{ .FieldType }}& {{ .Name }} +{{- end }} +) noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $op.OneofFields }} + if (!{{ .Name }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- end }} +{{- range $op.Fields }} +{{- if .IsRepeated }} + if ({{ .Name }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ .Name }}) + { +{{- if .IsMessage }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared<{{ .JsonArrayValue }}>(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ .JsonName }}"), Array); + } +{{- else if .IsBytes }} + Json->SetStringField(TEXT("{{ .JsonName }}"), FBase64::Encode({{ .Name }})); +{{- else if .IsMessage }} + Json->SetObjectField(TEXT("{{ .JsonName }}"), {{ .Name }}.ToJson()); +{{- else if .IsEnum }} + Json->SetNumberField(TEXT("{{ .JsonName }}"), static_cast({{ .Name }})); +{{- else if eq .ToJsonGuard "IsEmpty" }} + if (!{{ .Name }}.IsEmpty()) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else if eq .ToJsonGuard "NonZero" }} + if ({{ .Name }} != 0) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else }} + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); +{{- end }} +{{- end }} +{{- range $op.MapFields }} + if ({{ .Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ .Name }}) + { + MapObj->{{ .MapValueSetter }}(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ .JsonName }}"), MapObj); + } +{{- end }} + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("{{ $op.CaseName }}"), Json); +} +{{- end }} +} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.h.tmpl new file mode 100644 index 000000000..d75206465 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rtclient.ue.h.tmpl @@ -0,0 +1,76 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaWebSocketSubsystem.h" +#include "NakamaRtTypes.h" + +namespace Nakama +{ + class NakamaRealtimeClient + { + private: + TWeakObjectPtr WebSocketSubsystem; + + public: + explicit NakamaRealtimeClient(UGameInstance* InGi) + { + if (InGi == nullptr) + { + UE_LOG(LogNakama, Error, TEXT("NakamaRealtimeClient constructor received null GameInstance pointer.")); + return; + } + WebSocketSubsystem = InGi->GetSubsystem(); + } + ~NakamaRealtimeClient() + { + if (WebSocketSubsystem.IsValid()) + { + WebSocketSubsystem->Close(); + } + } + + NakamaRealtimeClient(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient& operator=(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient(NakamaRealtimeClient&&) = delete; + NakamaRealtimeClient& operator=(NakamaRealtimeClient&&) = delete; + + /** Connect (or reconnect) the WebSocket. Returns a future that resolves once the + * handshake completes or fails. */ + NAKAMA_API TNakamaFuture Connect( + const FNakamaWebSocketConnectionParams& Params + ) noexcept; +{{- range $op := $.RtOperations }} + + /** {{ $op.Comment }} */ + NAKAMA_API TNakamaFuture {{ $op.MessageName }}( +{{- range $idx, $f := $op.OneofFields }} + {{ $f.ParamType }} {{ $f.Name }}, +{{- end }} +{{- range $idx, $f := $op.Fields }} + {{ $f.ParamType }} {{ $f.Name }}, +{{- end }} +{{- range $idx, $f := $op.MapFields }} + const {{ $f.FieldType }}& {{ $f.Name }} = {} +{{- end }} + ) noexcept; +{{- end }} + }; +} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.cpp.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.cpp.tmpl new file mode 100644 index 000000000..324943b2d --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.cpp.tmpl @@ -0,0 +1,154 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtTypes.h" + +{{- /* Generate FromJson/ToJson implementations for each message */ -}} +{{- range $msg := $.Messages }} + +FNakamaRt{{ $msg.Name }} FNakamaRt{{ $msg.Name }}::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRt{{ $msg.Name }} Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $msg.Fields }} +{{- if .IsRepeated }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ .JsonName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if .IsMessage }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ .Name }}.Add({{ .MessageType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ .Name }}.Add({{ .ArrayItemExpr }}); +{{- end }} + } + } + } +{{- else if .IsBytes }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + FString Base64 = Json->GetStringField(TEXT("{{ .JsonName }}")); + if (!FBase64::Decode(Base64, Result.{{ .Name }})) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } +{{- else if .IsMessage }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ .JsonName }}"), NestedObj)) + { + Result.{{ .Name }} = {{ .MessageType }}::FromJson(*NestedObj); + } + } +{{- else if .IsEnum }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = static_cast<{{ .EnumType }}>(Json->GetIntegerField(TEXT("{{ .JsonName }}"))); + } +{{- else if ne .CastFromJson "" }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = {{ .CastFromJson }}(Json->{{ .JsonGetter }}(TEXT("{{ .JsonName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + Result.{{ .Name }} = Json->{{ .JsonGetter }}(TEXT("{{ .JsonName }}")); + } +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + if (Json->HasField(TEXT("{{ .JsonName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ .JsonName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ .Name }}.Add(Pair.Key, {{ .MapValueReader }}); + } + } + } +{{- end }} + return Result; +} + +TSharedPtr FNakamaRt{{ $msg.Name }}::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $msg.Fields }} +{{- if .IsRepeated }} + if ({{ .Name }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ .Name }}) + { +{{- if .IsMessage }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared<{{ .JsonArrayValue }}>(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ .JsonName }}"), Array); + } +{{- else if .IsBytes }} + Json->SetStringField(TEXT("{{ .JsonName }}"), FBase64::Encode({{ .Name }})); +{{- else if .IsMessage }} + Json->SetObjectField(TEXT("{{ .JsonName }}"), {{ .Name }}.ToJson()); +{{- else if .IsEnum }} + Json->SetNumberField(TEXT("{{ .JsonName }}"), static_cast({{ .Name }})); +{{- else if eq .ToJsonGuard "IsEmpty" }} + if (!{{ .Name }}.IsEmpty()) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else if eq .ToJsonGuard "NonZero" }} + if ({{ .Name }} != 0) + { + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); + } +{{- else }} + Json->{{ .JsonSetter }}(TEXT("{{ .JsonName }}"), {{ .Name }}); +{{- end }} +{{- end }} +{{- range $msg.MapFields }} + if ({{ .Name }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ .Name }}) + { + MapObj->{{ .MapValueSetter }}(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ .JsonName }}"), MapObj); + } +{{- end }} + return Json; +} +{{- end }} diff --git a/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.h.tmpl new file mode 100644 index 000000000..f9d8e6881 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/nakama-rttypes.ue.h.tmpl @@ -0,0 +1,53 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" + +#include "NakamaRtTypes.generated.h" + +// Forward declarations +{{- range $.Messages }} +struct FNakamaRt{{ .Name }}; +{{- end }} + +{{- /* Generate struct definitions for each proto message */ -}} +{{- range $msg := $.Messages }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRt{{ $msg.Name }} +{ + GENERATED_BODY() + + {{- range $msg.Fields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ .FieldType }} {{ .Name }}{{ .FieldDefault }}; + {{- end }} + {{- range $msg.MapFields }} + /** {{ .Comment }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ .FieldType }} {{ .Name }}; + {{- end }} + + static FNakamaRt{{ $msg.Name }} FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; +{{- end }} diff --git a/Nakama/Source/NakamaCore/Public/nakama-cpp/NakamaVersion.h b/cmd/codegen-modular/unreal/templates/types.ue.cpp.tmpl similarity index 69% rename from Nakama/Source/NakamaCore/Public/nakama-cpp/NakamaVersion.h rename to cmd/codegen-modular/unreal/templates/types.ue.cpp.tmpl index 3eb98ae29..b2405b6e3 100644 --- a/Nakama/Source/NakamaCore/Public/nakama-cpp/NakamaVersion.h +++ b/cmd/codegen-modular/unreal/templates/types.ue.cpp.tmpl @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Nakama Authors + * Copyright {{currentYear}} The Nakama Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ * limitations under the License. */ -#pragma once +/* This code is auto-generated. DO NOT EDIT. */ -#include -#include +#include "{{ $.Name }}Types.h" -NAKAMA_NAMESPACE_BEGIN +{{- template "session_jwt_helpers" . }} - NAKAMA_API const char* getNakamaSdkVersion(); +{{- template "all_fromjson_tojson" . }} -NAKAMA_NAMESPACE_END +{{- template "clientconfig_getbaseurl" . }} diff --git a/cmd/codegen-modular/unreal/templates/types.ue.h.tmpl b/cmd/codegen-modular/unreal/templates/types.ue.h.tmpl new file mode 100644 index 000000000..bb7350dd2 --- /dev/null +++ b/cmd/codegen-modular/unreal/templates/types.ue.h.tmpl @@ -0,0 +1,57 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "{{ $.Name }}Types.generated.h" + +{{- template "forward_declarations" . }} + +{{- template "enum_declarations" . }} + +{{- template "error_struct" . }} + +{{- template "errorcodes" $.Name }} + +{{- template "session_struct_decl" . }} + +{{- template "non_session_struct_declarations" . }} + +{{- template "auth_enum_decl" . }} + +/** Low-level API client configuration. */ +USTRUCT(BlueprintType) +struct {{ $.ApiExport }} {{ $.TypePrefix }}ClientConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{{ $.Name }}") + FString ServerKey; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{{ $.Name }}") + FString Host; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{{ $.Name }}") + int32 Port = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{{ $.Name }}") + bool bUseSSL = false; + + FString GetBaseUrl() const noexcept; +}; diff --git a/cmd/codegen-modular/unreal/typemap.go b/cmd/codegen-modular/unreal/typemap.go new file mode 100644 index 000000000..8314560a4 --- /dev/null +++ b/cmd/codegen-modular/unreal/typemap.go @@ -0,0 +1,241 @@ +package unreal + +// TypeEntry contains all traits for mapping a proto type to a target language. +type TypeEntry struct { + // Function signature types + Param string // Scalar parameter form (e.g., "const FString&", "int32") + RepeatedParam string // Repeated field parameter form (e.g., "const TArray&") + MapParam string // Map field parameter form (e.g., "const TMap&") + MapType string // Map type name without const/ref (e.g., "TMap") + + // JSON serialization (body building) + JsonMethod string // SetXxxField suffix: "String", "Number", "Bool", "Object" + JsonArrayValue string // FJsonValue subclass: "String", "Number", "Boolean", "Object" + + // Query string + QueryFormat string // Printf verb: "%s", "%d", "%lld" + EmptyCheck string // Guard strategy: "IsEmpty", "NonZero", "None", "NumEmpty" + + // Struct field declarations (UPROPERTY) + FieldType string // Scalar UPROPERTY type (e.g., "FString", "int32") + RepeatedFieldType string // Array UPROPERTY type (e.g., "TArray") + FieldDefault string // Default initializer (e.g., "", " = 0", " = false") + + // JSON deserialization (FromJson) + JsonGetter string // Getter method: "GetStringField", "GetIntegerField", etc. + CastFromJson string // Optional cast: "" or "static_cast" + ArrayItemExpr string // Repeated item read: "Item->AsString()", "static_cast(Item->AsNumber())" + NeedsHasCheck bool // Whether FromJson needs HasField guard (false for none-check types) + + // ToJson extras + NeedsEmptyGuard bool // true for strings/timestamps in ToJson +} + +// TypeMap resolves proto type names to target-language type traits. +type TypeMap interface { + Resolve(protoType string) (*TypeEntry, bool) +} + +type UnrealTypeMap struct { + entries map[string]*TypeEntry +} + +func NewUnrealTypeMap() *UnrealTypeMap { + stringEntry := &TypeEntry{ + Param: "const FString&", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "String", + JsonArrayValue: "String", + QueryFormat: "%s", + EmptyCheck: "IsEmpty", + + FieldType: "FString", + RepeatedFieldType: "TArray", + FieldDefault: "", + + JsonGetter: "GetStringField", + CastFromJson: "", + ArrayItemExpr: "Item->AsString()", + NeedsHasCheck: true, + NeedsEmptyGuard: true, + } + + boolEntry := &TypeEntry{ + Param: "bool", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Bool", + JsonArrayValue: "Boolean", + QueryFormat: "%s_bool", + EmptyCheck: "None", + + FieldType: "bool", + RepeatedFieldType: "TArray", + FieldDefault: " = false", + + JsonGetter: "GetBoolField", + CastFromJson: "", + ArrayItemExpr: "Item->AsBool()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + int32Entry := &TypeEntry{ + Param: "int32", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%d", + EmptyCheck: "NonZero", + + FieldType: "int32", + RepeatedFieldType: "TArray", + FieldDefault: " = 0", + + JsonGetter: "GetIntegerField", + CastFromJson: "", + ArrayItemExpr: "static_cast(Item->AsNumber())", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + uint32Entry := *int32Entry + uint32Entry.CastFromJson = "static_cast" + uint32Entry.JsonGetter = "GetNumberField" + uint32Entry.ArrayItemExpr = "static_cast(Item->AsNumber())" + + int64Entry := &TypeEntry{ + Param: "int64", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%lld", + EmptyCheck: "NonZero", + + FieldType: "int64", + RepeatedFieldType: "TArray", + FieldDefault: " = 0", + + JsonGetter: "GetNumberField", + CastFromJson: "static_cast", + ArrayItemExpr: "static_cast(Item->AsNumber())", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + uint64Entry := *int64Entry + uint64Entry.CastFromJson = "static_cast" + uint64Entry.ArrayItemExpr = "static_cast(Item->AsNumber())" + + floatEntry := &TypeEntry{ + Param: "float", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%f", + EmptyCheck: "NonZero", + + FieldType: "float", + RepeatedFieldType: "TArray", + FieldDefault: " = 0.0f", + + JsonGetter: "GetNumberField", + CastFromJson: "", + ArrayItemExpr: "Item->AsNumber()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + doubleEntry := &TypeEntry{ + Param: "double", + RepeatedParam: "const TArray&", + MapParam: "const TMap&", + MapType: "TMap", + + JsonMethod: "Number", + JsonArrayValue: "Number", + QueryFormat: "%f", + EmptyCheck: "NonZero", + + FieldType: "double", + RepeatedFieldType: "TArray", + FieldDefault: " = 0.0", + + JsonGetter: "GetNumberField", + CastFromJson: "", + ArrayItemExpr: "Item->AsNumber()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + bytesEntry := &TypeEntry{ + Param: "const TArray&", + RepeatedParam: "const TArray>&", + MapParam: "const TMap>&", + MapType: "TMap>", + + JsonMethod: "String", + JsonArrayValue: "String", + QueryFormat: "%s", + EmptyCheck: "NumEmpty", + + FieldType: "TArray", + RepeatedFieldType: "TArray>", + FieldDefault: "", + + JsonGetter: "GetStringField", + CastFromJson: "", + ArrayItemExpr: "Item->AsString()", + NeedsHasCheck: true, + NeedsEmptyGuard: false, + } + + return &UnrealTypeMap{ + entries: map[string]*TypeEntry{ + "string": stringEntry, + "google.protobuf.StringValue": stringEntry, + "google.protobuf.Timestamp": stringEntry, + + "bool": boolEntry, + "google.protobuf.BoolValue": boolEntry, + + "int32": int32Entry, + "google.protobuf.Int32Value": int32Entry, + "uint32": &uint32Entry, + "google.protobuf.UInt32Value": &uint32Entry, + + "int64": int64Entry, + "google.protobuf.Int64Value": int64Entry, + "uint64": &uint64Entry, + "google.protobuf.UInt64Value": &uint64Entry, + + "float": floatEntry, + "google.protobuf.FloatValue": floatEntry, + "double": doubleEntry, + "google.protobuf.DoubleValue": doubleEntry, + + "bytes": bytesEntry, + "google.protobuf.BytesValue": bytesEntry, + + }, + } +} + +func (m *UnrealTypeMap) Resolve(protoType string) (*TypeEntry, bool) { + e, ok := m.entries[protoType] + return e, ok +} diff --git a/cmd/codegen-modular/unreal/viewmodel.go b/cmd/codegen-modular/unreal/viewmodel.go new file mode 100644 index 000000000..f9e016356 --- /dev/null +++ b/cmd/codegen-modular/unreal/viewmodel.go @@ -0,0 +1,152 @@ +package unreal + +// Param represents a leading function parameter (Session or HttpKey) in generated code. +type Param struct { + Name string + Type string + Comment string +} + +// ResolvedField contains all metadata for a proto field after target-language +// type resolution. Used for both struct declarations (UPROPERTY, FromJson, ToJson) +// and method signatures (API functions, query params, body serialization). +type ResolvedField struct { + Name string // PascalCase name for use in generated code + JsonName string // Original proto field name for JSON serialization + Comment string // Documentation comment from the proto definition + + // Classification + IsRepeated bool + IsMap bool + IsMessage bool + IsEnum bool + + // Function signature / struct declaration + ParamType string // Full C++ parameter type (e.g., "const FString&", "int32") + FieldType string // UE field type for UPROPERTY (e.g., "FString", "int32", "TArray") + FieldDefault string // Default initializer (e.g., "", " = 0", " = false") + + // JSON method suffix (used by API method body templates for composition) + JsonMethod string // Setter suffix: "String", "Bool", "Number", "Object" + + // JSON full-form names (used by struct FromJson/ToJson templates directly) + JsonGetter string // "GetStringField", "GetIntegerField", "GetNumberField", "GetBoolField" + JsonSetter string // "SetStringField", "SetNumberField", "SetBoolField" + JsonArrayValue string // "FJsonValueString", "FJsonValueNumber", "FJsonValueBoolean" + + // JSON deserialization helpers (FromJson) + CastFromJson string // Optional cast: "" or "static_cast" + ArrayItemExpr string // Repeated item read: "Item->AsString()", "static_cast(Item->AsNumber())" + + // ToJson guard + ToJsonGuard string // Guard in ToJson: "" (always), "IsEmpty", "NonZero" + + // Query string / API method body + QueryFormat string // Printf verb for query string: "%s", "%d", "%lld" + EmptyCheck string // Guard strategy for query/body: "IsEmpty", "NonZero", "None", "NumEmpty" + + // Special types + IsBytes bool // bytes field needing Base64 encode/decode + MapValueSetter string // For map fields: "SetStringField" or "SetNumberField" + MapValueReader string // For map fields: "Pair.Value->AsString()" or "Pair.Value->AsNumber()" + + // Type references (message/enum fields only) + MessageType string // "FNakamaXxx" for message fields + EnumType string // "ENakamaXxx" for enum fields +} + +// MethodImpl describes a single generated function with all type resolution +// and field classification pre-computed. Both declaration (.h) and +// implementation (.cpp) templates consume this same type. +type MethodImpl struct { + Name string + Comment string + + // HTTP routing + HttpMethod string + Endpoint string + + // Authentication + AuthType string // "Basic", "Bearer", "HttpKey" + AuthToken string // C++ expression for the auth token value + + // Lead parameter (Session or HttpKey); nil for auth RPCs + LeadParam *Param + + // All request fields for the function signature + Fields []ResolvedField + MapFields []ResolvedField + + // Return type + HasReturn bool + ReturnType string // Type name without prefix (e.g., "Session") + AsyncResultType string // Result wrapper type name (e.g., "FNakamaSession" or "FNakamaVoid") + + // Pre-classified field groups for the function body + PathParams []ResolvedField + QueryFields []ResolvedField + + // Body specification + BodyMode string // "none", "scalar", "object", "wildcard" + BodyFieldName string // PascalCase name of the body field (scalar/object modes) + WildcardFields []ResolvedField // Normal fields for wildcard body serialization + WildcardMapFields []ResolvedField // Map fields for wildcard body serialization + UseStringBody bool // true: SendRequest with FString; false: MakeRequest with FJsonObject + + // High-level API (with argument flattening for ergonomic caller interface) + HighLevelParams []HighLevelParam // Function params with message fields flattened into sub-fields + FlattenedGroups []FlattenedGroup // Groups needing struct reconstruction in function body +} + +// HighLevelParam represents a function parameter in the high-level API. +type HighLevelParam struct { + Name string // PascalCase + ParamType string // C++ parameter type + Default string // Default value expression: "" for none, " = {}" etc. + + // Blueprint stored field metadata + FieldType string // UPROPERTY storage type (e.g., "FString", "int32") + FieldDefault string // Storage default initializer (e.g., "", " = 0") + IsScalarBody bool // true if this is a non-message body field (stored as TMap in BP) +} + +// FlattenedGroup represents a message field that was flattened into individual sub-fields. +type FlattenedGroup struct { + FieldName string // PascalCase name of the original field + FieldType string // UE type for struct reconstruction (e.g., "FNakamaAccountEmail") + SubFields []string // PascalCase names of sub-fields to assign +} + +// ResolvedMessage is a pre-resolved proto message with all field metadata computed. +type ResolvedMessage struct { + Name string + Comment string + Fields []ResolvedField + MapFields []ResolvedField + IsSession bool // Special handling for Session types +} + +// ResolvedEnumField is a single value in a resolved enum. +type ResolvedEnumField struct { + Name string + Value int + IsLast bool +} + +// ResolvedEnum is a pre-resolved proto enum. +type ResolvedEnum struct { + Name string + Comment string + Fields []ResolvedEnumField +} + +// ResolvedRtOperation represents a single realtime WebSocket message handler, +// derived from a oneof field in the Envelope message. +type ResolvedRtOperation struct { + CaseName string // The oneof case name (used in the WebSocket protocol) + MessageName string // PascalCase message type name + Comment string // Documentation comment + Fields []ResolvedField // Pre-resolved normal fields + MapFields []ResolvedField // Pre-resolved map fields + OneofFields []ResolvedField // Pre-resolved oneof sub-fields (treated as optional strings) +} diff --git a/cmd/codegen-modular/yacg/api.go b/cmd/codegen-modular/yacg/api.go new file mode 100644 index 000000000..8b12719f5 --- /dev/null +++ b/cmd/codegen-modular/yacg/api.go @@ -0,0 +1,178 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package yacg + +import ( + "bytes" + "fmt" + "log" + "os" + "regexp" + "strings" + + "github.com/emicklei/proto" +) + +type Api struct { + // Use slices to preserve order of proto messages + Enums []*ProtoEnum + Messages []*ProtoMessage + Rpcs []*ProtoRpc + + EnumsByName map[string]*ProtoEnum + MessagesByName map[string]*ProtoMessage + RpcsByName map[string]*ProtoRpc +} + +func (api *Api) addFile(protoFile string) error { + fileBytes, err := os.ReadFile(protoFile) + if err != nil { + return fmt.Errorf("failed to read file %s: %s", protoFile, err.Error()) + } + + parser := proto.NewParser(bytes.NewReader(fileBytes)) + parsedProto, err := parser.Parse() + if err != nil { + return fmt.Errorf("failed to parse proto file %s: %s", protoFile, err.Error()) + } + + proto.Walk( + parsedProto, + proto.WithEnum( + func(enum *proto.Enum) { + comment := "" + if enum.Comment != nil { + comment = enum.Comment.Message() + + // Find everything between square brackets + re := regexp.MustCompile(`\[(.*?)\]`) + matches := re.FindAllStringSubmatch(comment, -1) + for _, match := range matches { + // match[0] is the entire match, match[1] is the first submatch. + comment = match[1] + } + } + + // Build fully qualified enum name including parent message if nested + enumName := enum.Name + if enum.Parent != nil { + if parentMsg, ok := enum.Parent.(*proto.Message); ok { + enumName = parentMsg.Name + "_" + enum.Name + } + } + + visitor := &enumVisitor{ + Enum: &ProtoEnum{ + Comment: strings.Trim(comment, " "), + Fields: make([]*enumField, 0), + Name: enumName, + }, + } + for _, each := range enum.Elements { + each.Accept(visitor) + } + api.Enums = append(api.Enums, visitor.Enum) + api.EnumsByName[visitor.Enum.Name] = visitor.Enum + }, + ), + proto.WithMessage( + func(message *proto.Message) { + if message.Name == "google.protobuf.EnumValueOptions" { + return + } + + comment := "" + if message.Comment != nil { + comment = message.Comment.Message() + } + visitor := &messageVisitor{ + Message: &ProtoMessage{ + Comment: comment, + Fields: make([]*proto.NormalField, 0), + MapFields: make([]*proto.MapField, 0), + Name: message.Name, + }, + } + for _, each := range message.Elements { + each.Accept(visitor) + } + api.Messages = append(api.Messages, visitor.Message) + api.MessagesByName[visitor.Message.Name] = visitor.Message + }, + ), + proto.WithRPC( + func(rpc *proto.RPC) { + comment := "" + if rpc.Comment != nil { + comment = rpc.Comment.Message() + comment = strings.TrimSpace(comment) + } + + resolveType := func(fullTypeName string) *ProtoMessage { + if fullTypeName == "google.protobuf.Empty" { + return nil + } else { + // We get something like `api.MyRequestType`, so strip after the last dot. + stripped := fullTypeName[strings.LastIndex(fullTypeName, ".")+1:] + + t, ok := api.MessagesByName[stripped] + if !ok { + log.Fatalf("Unable to find type %s for %s", fullTypeName, rpc.Name) + } + return t + } + } + + requestType := resolveType(rpc.RequestType) + returnType := resolveType(rpc.ReturnsType) + + visitor := &rpcVisitor{ + Rpc: &ProtoRpc{ + Comment: comment, + RequestType: requestType, + ReturnType: returnType, + Name: rpc.Name, + }, + } + + for _, each := range rpc.Elements { + each.Accept(visitor) + } + api.Rpcs = append(api.Rpcs, visitor.Rpc) + api.RpcsByName[visitor.Rpc.Name] = visitor.Rpc + }, + ), + ) + + return nil +} + +func LoadApi(protoFiles []string) (Api, error) { + api := Api{ + Enums: make([]*ProtoEnum, 0), + Messages: make([]*ProtoMessage, 0), + Rpcs: make([]*ProtoRpc, 0), + + EnumsByName: make(map[string]*ProtoEnum, 0), + MessagesByName: make(map[string]*ProtoMessage, 0), + RpcsByName: make(map[string]*ProtoRpc, 0), + } + + // Load file by file... + // Updates maps internally, so each subsequent + // file will have previous context to work with. + for _, f := range protoFiles { + api.addFile(f) + } + + return api, nil +} diff --git a/cmd/codegen-modular/yacg/go.mod b/cmd/codegen-modular/yacg/go.mod new file mode 100644 index 000000000..2cf446648 --- /dev/null +++ b/cmd/codegen-modular/yacg/go.mod @@ -0,0 +1,5 @@ +module heroiclabs.com/yacg + +go 1.22.2 + +require github.com/emicklei/proto v1.14.3 // indirect diff --git a/cmd/codegen-modular/yacg/go.sum b/cmd/codegen-modular/yacg/go.sum new file mode 100644 index 000000000..315bfd0f3 --- /dev/null +++ b/cmd/codegen-modular/yacg/go.sum @@ -0,0 +1,2 @@ +github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q= +github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= diff --git a/cmd/codegen-modular/yacg/modules/api_mapper.go b/cmd/codegen-modular/yacg/modules/api_mapper.go new file mode 100644 index 000000000..b84c25217 --- /dev/null +++ b/cmd/codegen-modular/yacg/modules/api_mapper.go @@ -0,0 +1,59 @@ +package modules + +import ( + "heroiclabs.com/yacg" +) + +type DataDecl struct { + Type string + Name string + Comment string +} + +type Type struct { + DataDecl + Members []DataDecl +} + +type Function struct { + DataDecl + + Params []DataDecl + ReturnType Type + + // Local function variable declarations. + Locals map[string]any +} + +type Enum struct { + DataDecl + Values map[string]EnumValue +} +type EnumValue struct { + Value int + Comment string +} + +type ApiMap struct { + Enums []Enum + Types []Type + Funcs []Function +} + +type ApiMapper interface { + // Maps the API to a set of functions. + MapApi(api yacg.Api, nameResolver NameResolver) (ApiMap, error) +} + +type HttpApiMapper interface { + ApiMapper + + // Map an RPC to one or more functions (for overloading support). + MapRpc(rpc *yacg.ProtoRpc, api yacg.Api, nameResolver NameResolver) ([]Function, error) + + // Map an Enum to sdk-specific representation + MapEnum(rpc *yacg.ProtoRpc, api yacg.Api, nameResolver NameResolver) (Enum, error) + + // Maps a message to a Type representation + MapMessage(rpc *yacg.ProtoMessage, api yacg.Api, nameResolver NameResolver) (Type, error) +} diff --git a/cmd/codegen-modular/yacg/modules/module.go b/cmd/codegen-modular/yacg/modules/module.go new file mode 100644 index 000000000..4572136d2 --- /dev/null +++ b/cmd/codegen-modular/yacg/modules/module.go @@ -0,0 +1,99 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package modules + +import ( + "fmt" + "log" + "os" + "text/template" + + "heroiclabs.com/yacg" +) + +type Requirements struct { + Protos []string +} + +type Production struct { + Template string + FuncMap template.FuncMap + NameResolver NameResolver + Mapper ApiMapper + Output string +} + +type Module struct { + Requires Requirements + Produces []Production +} + +type CompiledModule struct { + Api yacg.Api + Produces []Production +} + +func (m Module) Compile() (CompiledModule, error) { + api, err := yacg.LoadApi(m.Requires.Protos) + if err != nil { + return CompiledModule{}, err + } + + compiled := CompiledModule{ + Api: api, + Produces: m.Produces, + } + + return compiled, nil +} + +func (cm CompiledModule) Generate(outPath string) error { + err := os.MkdirAll(outPath, 0755) + if err != nil { + return err + } + + for _, p := range cm.Produces { + // + // Read and parse the template file + tmpl := template.New("codegen").Funcs(p.FuncMap) + + tmplBytes, err := os.ReadFile(p.Template) + if err != nil { + log.Fatalf("Failed to read template file '%s': %s", p.Template, err.Error()) + } + + if _, err = tmpl.Parse(string(tmplBytes)); err != nil { + log.Fatalf("Failed to parse template '%s': %s", p.Template, err.Error()) + } + + // + // Generate the code from the mapper using the parsed Template. + outFilePath := fmt.Sprintf("%s/%s", outPath, p.Output) + outFile, err := os.Create(outFilePath) + if err != nil { + log.Fatalf("Failed to create file %s: %s", outFilePath, err) + } + + // TODO: Move to compile step + apiMap, err := p.Mapper.MapApi(cm.Api, p.NameResolver) + if err != nil { + log.Fatalf("Failed to create the API map: %s", err) + } + + if err := tmpl.Execute(outFile, apiMap); err != nil { + log.Printf("Failed to execute template '%s': %s", p.Template, err.Error()) + } + } + + return nil +} diff --git a/cmd/codegen-modular/yacg/modules/name_resolver.go b/cmd/codegen-modular/yacg/modules/name_resolver.go new file mode 100644 index 000000000..958192f77 --- /dev/null +++ b/cmd/codegen-modular/yacg/modules/name_resolver.go @@ -0,0 +1,72 @@ +package modules + +// TypeEntry contains all traits for mapping a proto type to a target language. +type TypeEntry struct { + // Function signature types + Param string // Scalar parameter form (e.g., "const FString&", "int32") + RepeatedParam string // Repeated field parameter form (e.g., "const TArray&") + MapParam string // Map field parameter form (e.g., "const TMap&") + MapType string // Map type name without const/ref (e.g., "TMap") + + // Struct field declarations (e.g. UPROPERTY) + FieldType string // Scalar field type (e.g., "FString", "int32") + RepeatedFieldType string // Array field type (e.g., "TArray") + FieldDefault string // Default initializer (e.g., "", " = 0", " = false") + + // Query string + QueryFormat string // Printf verb: "%s", "%d", "%lld" + EmptyCheck string // Guard strategy: "IsEmpty", "NonZero", "None", "NumEmpty" + + // JSON serialization (body building) + JsonMethod string // SetXxxField suffix: "String", "Number", "Bool", "Object" + JsonArrayValue string // FJsonValue subclass: "String", "Number", "Boolean", "Object" + + // JSON deserialization (FromJson) + JsonGetter string // Getter method: "GetStringField", "GetIntegerField", etc. + CastFromJson string // Optional cast: "" or "static_cast" + ArrayItemExpr string // Repeated item read: "Item->AsString()", "static_cast(Item->AsNumber())" + NeedsHasCheck bool // Whether FromJson needs HasField guard (false for none-check types) + + // ToJson extras + NeedsEmptyGuard bool // true for strings/timestamps in ToJson +} + +type NameResolveContext int + +const ( + TypeName NameResolveContext = iota + FuncName + FieldName + + EnumType + + FieldType + RepeatedFieldType + FieldDefault + + MapType + + Param + RepeatedParam + MapParam + + JsonMethod + JsonArrayType + JsonArrayValue + QueryFormat + EmptyCheck + + JsonGetter + JsonSetter + CastFromJson + ArrayItemExpr + // NeedsHasCheck + // NeedsEmptyGuard + + SENTINEL_STD_RESOLVE_CTX // Used for extending the ResolveContext +) + +// NameResolver transforms strings to target language with a given context. +type NameResolver interface { + Resolve(input string, ctx NameResolveContext) string +} diff --git a/cmd/codegen-modular/yacg/proto_visitors.go b/cmd/codegen-modular/yacg/proto_visitors.go new file mode 100644 index 000000000..5afa8fc99 --- /dev/null +++ b/cmd/codegen-modular/yacg/proto_visitors.go @@ -0,0 +1,214 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package yacg + +import ( + "regexp" + "slices" + "strings" + "text/scanner" + + "github.com/emicklei/proto" +) + +// -------------------- +// Enums +type ProtoEnum struct { + Comment string + Fields []*enumField + Name string +} + +type enumField struct { + *proto.EnumField + Input string + Output string +} + +type enumVisitor struct { + proto.NoopVisitor + Enum *ProtoEnum +} + +func (v *enumVisitor) VisitEnumField(ef *proto.EnumField) { + if ef.Comment == nil { + ef.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + var input, output string + for _, each := range ef.Elements { + option, ok := each.(*proto.Option) + if ok { + if strings.Contains(option.Name, "input") { + input = option.Constant.Source + } else if strings.Contains(option.Name, "output") { + output = option.Constant.Source + } + } + } + + v.Enum.Fields = append(v.Enum.Fields, &enumField{ + EnumField: ef, + Input: input, + Output: output, + }) +} + +// -------------------- +// Messages +type ProtoMessage struct { + Comment string + Fields []*proto.NormalField + MapFields []*proto.MapField + OneofFields []*proto.OneOfField + Name string +} + +type messageVisitor struct { + proto.NoopVisitor + Message *ProtoMessage +} + +func (v *messageVisitor) VisitMapField(mf *proto.MapField) { + if mf.Comment == nil { + mf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.MapFields = append(v.Message.MapFields, mf) +} + +func (v *messageVisitor) VisitNormalField(nf *proto.NormalField) { + if nf.Comment == nil { + nf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.Fields = append(v.Message.Fields, nf) +} + +func (v *messageVisitor) VisitOneof(oneof *proto.Oneof) { + for _, o := range oneof.Elements { + o.Accept(v) + } +} + +func (v *messageVisitor) VisitOneofField(oneof *proto.OneOfField) { + // Sometimes we can get types like more qualified + // types like api.MyMessage, so take only the bit after last dot. + oneof.Name = oneof.Name[strings.LastIndex(oneof.Name, ".")+1:] + + v.Message.OneofFields = append(v.Message.OneofFields, oneof) +} + +// -------------------- +// RPCs + +type ProtoRpc struct { + Name string + Comment string + RequestType *ProtoMessage + ReturnType *ProtoMessage + Endpoint string + Method string + PathParams []string + QueryParams []string + BodyParams []string + BodyField string // The field name to use as the body (from google.api.http body option) +} + +type rpcVisitor struct { + proto.NoopVisitor + Rpc *ProtoRpc +} + +func tryGetHttpMethod(str string) (string, bool) { + if strings.Contains(str, "post") { + return "POST", true + } + if strings.Contains(str, "get") { + return "GET", true + } + if strings.Contains(str, "delete") { + return "DELETE", true + } + if strings.Contains(str, "put") { + return "PUT", true + } + + return "", false +} + +func (v *rpcVisitor) VisitOption(o *proto.Option) { + if o.Constant.IsString { + v.Rpc.Endpoint = o.Constant.Source + } + + // + // Sometimes we have an option like (google.api.http).post + method, found := tryGetHttpMethod(o.Name) + if found { + v.Rpc.Method = method + } + + // + // And sometimes the method is hidden inside option props. + if o.Name == "(google.api.http)" { + for _, l := range o.Constant.OrderedMap { + method, found := tryGetHttpMethod(l.Name) + if found { + v.Rpc.Method = method + v.Rpc.Endpoint = l.Literal.Source + } + // Extract the body field specification + if l.Name == "body" { + v.Rpc.BodyField = l.Literal.Source + } + } + } + + v.Rpc.PathParams = make([]string, 0) + v.Rpc.QueryParams = make([]string, 0) + v.Rpc.BodyParams = make([]string, 0) + + // + // Path params + paramRegex := regexp.MustCompile(`\{([a-zA-Z0-9_]*)\}`) + matches := paramRegex.FindAllStringSubmatch(v.Rpc.Endpoint, -1) + for _, m := range matches { + v.Rpc.PathParams = append(v.Rpc.PathParams, m[1]) + } + + // + // Body/Query Params + if v.Rpc.BodyField == "*" { + for _, f := range v.Rpc.RequestType.Fields { + if !slices.Contains(v.Rpc.PathParams, f.Name) { + v.Rpc.BodyParams = append(v.Rpc.BodyParams, f.Name) + } + } + } else if method == "GET" || v.Rpc.BodyField == "" { + if v.Rpc.RequestType != nil { + for _, f := range v.Rpc.RequestType.Fields { + if !slices.Contains(v.Rpc.PathParams, f.Name) { + v.Rpc.QueryParams = append(v.Rpc.QueryParams, f.Name) + } + } + } + } else if v.Rpc.BodyField != "" { + v.Rpc.BodyParams = append(v.Rpc.BodyParams, v.Rpc.BodyField) + } +} diff --git a/cmd/codegen/.gitignore b/cmd/codegen/.gitignore new file mode 100644 index 000000000..ecb5fa033 --- /dev/null +++ b/cmd/codegen/.gitignore @@ -0,0 +1,2 @@ +build/ +proto/ diff --git a/cmd/codegen/go.mod b/cmd/codegen/go.mod new file mode 100644 index 000000000..9c8c0fa1f --- /dev/null +++ b/cmd/codegen/go.mod @@ -0,0 +1,8 @@ +module heroic-labs.com/nakama/sdk/codegen + +go 1.25.5 + +require ( + github.com/emicklei/proto v1.14.2 + github.com/golang-cz/textcase v1.2.1 +) diff --git a/cmd/codegen/go.sum b/cmd/codegen/go.sum new file mode 100644 index 000000000..60ffe8abe --- /dev/null +++ b/cmd/codegen/go.sum @@ -0,0 +1,4 @@ +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/golang-cz/textcase v1.2.1 h1:0xRtKo+abtJojre5ONjuMzyg9fSfiKBj5bWZ6fpTYxI= +github.com/golang-cz/textcase v1.2.1/go.mod h1:aWsQknYwxtTS2zSCrGGoRIsxmzjsHomRqLeMeVb+SKU= diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go new file mode 100644 index 000000000..f444caf08 --- /dev/null +++ b/cmd/codegen/main.go @@ -0,0 +1,1207 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "maps" + "os" + "path/filepath" + "regexp" + "slices" + "strings" + "text/scanner" + "text/template" + "time" + + "github.com/emicklei/proto" + "github.com/golang-cz/textcase" +) + +var bracketContentRe = regexp.MustCompile(`\[(.*?)\]`) + +type Api struct { + // Use slices to preserve order of proto messages + Enums []*visitedEnum + Messages []*visitedMessage + Rpcs []*visitedRpc + + EnumsByName map[string]*visitedEnum + MessagesByName map[string]*visitedMessage + RpcsByName map[string]*visitedRpc + + // TypePrefix is prepended to message names in generated code (e.g. "FNakama" or "FSatori"). + TypePrefix string +} + +func (api *Api) addFile(protoFile string) error { + fileBytes, err := os.ReadFile(protoFile) + if err != nil { + return fmt.Errorf("failed to read file %s: %s", protoFile, err.Error()) + } + + parser := proto.NewParser(bytes.NewReader(fileBytes)) + parsedProto, err := parser.Parse() + if err != nil { + return fmt.Errorf("failed to parse proto file %s: %s", protoFile, err.Error()) + } + + proto.Walk( + parsedProto, + proto.WithEnum( + func(enum *proto.Enum) { + comment := "" + if enum.Comment != nil { + comment = enum.Comment.Message() + + // Find everything between square brackets + matches := bracketContentRe.FindAllStringSubmatch(comment, -1) + for _, match := range matches { + // match[0] is the entire match, match[1] is the first submatch. + comment = match[1] + } + } + + // Build fully qualified enum name including parent message if nested + enumName := enum.Name + if enum.Parent != nil { + if parentMsg, ok := enum.Parent.(*proto.Message); ok { + enumName = parentMsg.Name + "_" + enum.Name + } + } + + visitor := &enumVisitor{ + Enum: &visitedEnum{ + Comment: strings.Trim(comment, " "), + Fields: make([]*enumField, 0), + Name: enumName, + }, + } + for _, each := range enum.Elements { + each.Accept(visitor) + } + api.Enums = append(api.Enums, visitor.Enum) + api.EnumsByName[visitor.Enum.Name] = visitor.Enum + }, + ), + proto.WithMessage( + func(message *proto.Message) { + if message.Name == "google.protobuf.EnumValueOptions" { + return + } + + comment := "" + if message.Comment != nil { + comment = message.Comment.Message() + } + visitor := &messageVisitor{ + Message: &visitedMessage{ + Comment: comment, + Fields: make([]*proto.NormalField, 0), + MapFields: make([]*proto.MapField, 0), + Name: message.Name, + }, + } + for _, each := range message.Elements { + each.Accept(visitor) + } + api.Messages = append(api.Messages, visitor.Message) + api.MessagesByName[visitor.Message.Name] = visitor.Message + }, + ), + proto.WithRPC( + func(rpc *proto.RPC) { + comment := "" + if rpc.Comment != nil { + comment = rpc.Comment.Message() + comment = strings.TrimSpace(comment) + } + + resolveType := func(fullTypeName string) *visitedMessage { + if fullTypeName == "google.protobuf.Empty" { + return nil + } else { + // We get something like `api.MyRequestType`, so strip after the last dot. + stripped := stripAfterLastDot(fullTypeName) + + t, ok := api.MessagesByName[stripped] + if !ok { + log.Fatalf("Unable to find type %s for %s", fullTypeName, rpc.Name) + } + return t + } + } + + requestType := resolveType(rpc.RequestType) + returnType := resolveType(rpc.ReturnsType) + + visitor := &rpcVisitor{ + Rpc: &visitedRpc{ + Comment: comment, + RequestType: requestType, + ReturnType: returnType, + Name: rpc.Name, + }, + } + + for _, each := range rpc.Elements { + each.Accept(visitor) + } + api.Rpcs = append(api.Rpcs, visitor.Rpc) + api.RpcsByName[visitor.Rpc.Name] = visitor.Rpc + }, + ), + ) + + return nil +} + +func loadApi(protoFiles []string, prefix string) (Api, error) { + api := Api{ + Enums: make([]*visitedEnum, 0), + Messages: make([]*visitedMessage, 0), + Rpcs: make([]*visitedRpc, 0), + + EnumsByName: make(map[string]*visitedEnum, 0), + MessagesByName: make(map[string]*visitedMessage, 0), + RpcsByName: make(map[string]*visitedRpc, 0), + + TypePrefix: prefix, + } + + // Load file by file... + // Updates maps internally, so each subsequent + // file will have previous context to work with. + for _, f := range protoFiles { + if err := api.addFile(f); err != nil { + return api, err + } + } + + return api, nil +} + +type fileList []string + +func (f *fileList) String() string { + return fmt.Sprint(*f) +} +func (f *fileList) Set(value string) error { + *f = append(*f, value) + return nil +} + +func main() { + // + // Parse command line args. + argTmpl := flag.String("template", "", "template file path.") + argPrefix := flag.String("prefix", "FNakama", "type prefix for generated struct names (e.g. FNakama, FSatori).") + + var argProtoFiles fileList + flag.Var(&argProtoFiles, "proto", "list of proto files to parse. Proto files provided later can depend on files provided earlier.") + + flag.Parse() + + if *argTmpl == "" { + log.Fatalf("Template file is not given. Please provide it via --template.") + } + if len(argProtoFiles) == 0 { + log.Fatalf("No proto files are given. Please provide one or more proto files via --proto.") + } + + // + // Parse the API files and load structures. + api, err := loadApi(argProtoFiles, *argPrefix) + if err != nil { + log.Fatalf("Failed to load API: %s", err.Error()) + } + + // + // Make function maps that we will use in templates. + generalFuncMap := getGeneralFuncMap(api) + cppFuncMap := getCppFuncMap(api) + unrealFuncMap := getUnrealFuncMap(api) + + combinedFuncMap := mergeFuncMaps(generalFuncMap, cppFuncMap, unrealFuncMap) + + // + // Read and parse the template file (with partials) + tmpl := template.New("codegen").Funcs(combinedFuncMap) + + // Load partial templates (_*.tmpl) from the same directory as the main template + partialPattern := filepath.Join(filepath.Dir(*argTmpl), "_*.tmpl") + partials, err := filepath.Glob(partialPattern) + if err != nil { + log.Fatalf("Failed to glob partial templates %s: %s", partialPattern, err.Error()) + } + for _, p := range partials { + pBytes, err := os.ReadFile(p) + if err != nil { + log.Fatalf("Failed to read partial template %s: %s", p, err.Error()) + } + if _, err = tmpl.Parse(string(pBytes)); err != nil { + log.Fatalf("Failed to parse partial template %s: %s", p, err.Error()) + } + } + + bytes, err := os.ReadFile(*argTmpl) + if err != nil { + log.Fatalf("Failed to read template file '%s': %s", *argTmpl, err.Error()) + } + + if _, err = tmpl.Parse(string(bytes)); err != nil { + log.Fatalf("Failed to parse template '%s': %s", *argTmpl, err.Error()) + } + + // + // Generate the code from parsed API using the parsed Template. + if err := tmpl.Execute(os.Stdout, api); err != nil { + log.Fatalf("Failed to execute template '%s': %s", *argTmpl, err.Error()) + } +} + +// -------------------- +// Proto visitors + +/* TODO: Review struct/func privacy */ + +// -------------------- +// Enums +type enumField struct { + *proto.EnumField + Input string + Output string +} + +type visitedEnum struct { + Comment string + Fields []*enumField + Name string +} + +type enumVisitor struct { + proto.NoopVisitor + Enum *visitedEnum +} + +func (v *enumVisitor) VisitEnumField(ef *proto.EnumField) { + if ef.Comment == nil { + ef.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + var input, output string + for _, each := range ef.Elements { + option, ok := each.(*proto.Option) + if ok { + if strings.Contains(option.Name, "input") { + input = option.Constant.Source + } else if strings.Contains(option.Name, "output") { + output = option.Constant.Source + } + } + } + + v.Enum.Fields = append(v.Enum.Fields, &enumField{ + EnumField: ef, + Input: input, + Output: output, + }) +} + +// -------------------- +// Messages +type visitedMessage struct { + Comment string + Fields []*proto.NormalField + MapFields []*proto.MapField + OneofFields []*proto.OneOfField + Name string +} + +type messageVisitor struct { + proto.NoopVisitor + Message *visitedMessage +} + +func (v *messageVisitor) VisitMapField(mf *proto.MapField) { + if mf.Comment == nil { + mf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.MapFields = append(v.Message.MapFields, mf) +} + +func (v *messageVisitor) VisitNormalField(nf *proto.NormalField) { + if nf.Comment == nil { + nf.Comment = &proto.Comment{ + Position: scanner.Position{}, + Lines: []string{}, + } + } + v.Message.Fields = append(v.Message.Fields, nf) +} + +func (v *messageVisitor) VisitOneof(oneof *proto.Oneof) { + for _, o := range oneof.Elements { + o.Accept(v) + } +} + +func (v *messageVisitor) VisitOneofField(oneof *proto.OneOfField) { + // Sometimes we can get more qualified types like api.MyMessage, + // so take only the bit after last dot. Copy to avoid mutating the parsed proto. + field := *oneof + field.Name = stripAfterLastDot(field.Name) + + v.Message.OneofFields = append(v.Message.OneofFields, &field) +} + +// -------------------- +// RPCs +type visitedRpc struct { + Name string + Comment string + RequestType *visitedMessage + ReturnType *visitedMessage + Endpoint string + Method string + QueryParams []string + BodyField string // The field name to use as the body (from google.api.http body option) +} + +type rpcVisitor struct { + proto.NoopVisitor + Rpc *visitedRpc +} + +var httpMethods = map[string]string{ + "get": "GET", + "post": "POST", + "put": "PUT", + "delete": "DELETE", + "patch": "PATCH", +} + +func tryGetHttpMethod(str string) (string, bool) { + // Exact match first (e.g. "post" from OrderedMap keys). + if method, ok := httpMethods[str]; ok { + return method, true + } + // Suffix match for qualified names like "(google.api.http).post". + if i := strings.LastIndex(str, "."); i >= 0 { + if method, ok := httpMethods[str[i+1:]]; ok { + return method, true + } + } + return "", false +} + +func (v *rpcVisitor) VisitOption(o *proto.Option) { + if o.Constant.IsString { + v.Rpc.Endpoint = o.Constant.Source + } + + // + // Sometimes we have an option like (google.api.http).post + method, found := tryGetHttpMethod(o.Name) + if found { + v.Rpc.Method = method + } + + // + // And sometimes the method is hidden inside option props. + if o.Name == "(google.api.http)" { + for _, l := range o.Constant.OrderedMap { + method, found := tryGetHttpMethod(l.Name) + if found { + v.Rpc.Method = method + v.Rpc.Endpoint = l.Literal.Source + } + // Extract the body field specification + if l.Name == "body" { + v.Rpc.BodyField = l.Literal.Source + } + } + } + + paramRegex := regexp.MustCompile(`\{([a-zA-Z0-9_]*)\}`) + matches := paramRegex.FindAllStringSubmatch(v.Rpc.Endpoint, -1) + + v.Rpc.QueryParams = make([]string, 0, len(matches)) + for _, m := range matches { + v.Rpc.QueryParams = append(v.Rpc.QueryParams, m[1]) + } +} + +// -------------------- +// Func maps + +// stripAfterLastDot returns the substring after the last dot, or the full string if there is no dot. +func stripAfterLastDot(s string) string { + if i := strings.LastIndex(s, "."); i >= 0 { + return s[i+1:] + } + return s +} + +func getGeneralFuncMap(api Api) template.FuncMap { + fnMap := template.FuncMap{ + "camelCase": textcase.CamelCase, + "pascalCase": textcase.PascalCase, + "snakeCase": textcase.SnakeCase, + "upperCase": strings.ToUpper, + "trimPrefix": strings.TrimPrefix, + "containsString": strings.Contains, + "containsInSlice": func(slice []string, value string) bool { + return slices.Contains(slice, value) + }, + "containsEnum": func(key string) bool { + _, ok := api.EnumsByName[key] + return ok + }, + "isEnumType": func(fieldType string) bool { + _, ok := api.EnumsByName[fieldType] + return ok + }, + "containsMessage": func(key string) bool { + _, ok := api.MessagesByName[key] + return ok + }, + "getMessage": func(key string) *visitedMessage { + result, _ := api.MessagesByName[key] + return result + }, + "minus": func(a, b int) int { + return a - b + }, + "lowerCase": func(s string) string { + if len(s) == 0 { + return s + } + return strings.ToLower(s[:1]) + s[1:] + }, + "appendString": func(s1 string, s2 string) string { + return s1 + s2 + }, + "isEnumInput": func(messageName string) bool { + for _, enum := range api.EnumsByName { + for _, field := range enum.Fields { + if field.Input == messageName { + return true + } + } + } + return false + }, + "isLastField": func(slice []*proto.NormalField, index int) bool { + return index == len(slice)-1 + }, + "isLastMapElement": func(slice []*proto.MapField, index int) bool { + return index == len(slice)-1 + }, + "isLastEnumField": func(slice []*enumField, index int) bool { + return index == len(slice)-1 + }, + "currentYear": func() int { + return time.Now().Year() + }, + "uniqueReturnTypes": func(rpcs []*visitedRpc) []string { + seen := make(map[string]bool) + var result []string + for _, rpc := range rpcs { + if rpc.ReturnType != nil && !seen[rpc.ReturnType.Name] { + seen[rpc.ReturnType.Name] = true + result = append(result, rpc.ReturnType.Name) + } + } + return result + }, + "stripDot": stripAfterLastDot, + } + + return fnMap +} + +func getCppFuncMap(api Api) template.FuncMap { + isEnum := func(fieldType string) bool { + _, ok := api.EnumsByName[fieldType] + return ok + } + + fnMap := template.FuncMap{ + "cppIsReservedKeyword": func(s string) bool { + _, isKw := cppKeywords[s] + return isKw + }, + + "cppReservedKeywordPostfix": func(s string) string { + _, isKw := cppKeywords[s] + if isKw { + return s + "_" + } + return s + }, + + "cppMapFieldType": func(fieldType string) string { + var cppType string + + switch fieldType { + case "string", + "google.protobuf.StringValue", + "google.protobuf.Timestamp", + "int64", "uint64": + cppType = "NStringMap" + + case "bool", "float", "double": + cppType = fmt.Sprintf("std::unordered_map", fieldType) + + case "int32": + cppType = fmt.Sprintf("std::unordered_map", fieldType) + + default: + cppType = fmt.Sprintf("std::vector", fieldType) + + } + return cppType + }, + + "cppFieldType": func(fieldType string, isRepeated bool) string { + var cppType string + + // Handle enums as int32_t + if isEnum(fieldType) { + if isRepeated { + return "std::vector" + } + return "int32_t" + } + + if isRepeated { + switch fieldType { + case "string", + "google.protobuf.StringValue": + cppType = "std::vector" + + case "google.protobuf.Timestamp": + cppType = "std::vector" + + default: + cppType = fmt.Sprintf("std::vector", fieldType) + + } + } else { + switch fieldType { + case "int32", "google.protobuf.Int32Value": + cppType = "int32_t" + + case "uint32": + cppType = "uint32_t" + + case "float": + cppType = "float" + + case "double": + cppType = "double" + + case "bool", "google.protobuf.BoolValue": + cppType = "bool" + + case "string", + "google.protobuf.StringValue", + "int64", + "uint64": + cppType = "std::string" + + case "google.protobuf.Struct": + cppType = "std::unordered_map" + + case "google.protobuf.Timestamp": + cppType = "NTimestamp" + + default: + cppType = fmt.Sprintf("N%s", fieldType) + } + } + + return cppType + }, + } + + return fnMap +} + +func getUnrealFuncMap(api Api) template.FuncMap { + isEnum := func(fieldType string) bool { + _, ok := api.EnumsByName[fieldType] + return ok + } + + // enumUEName returns the Unreal enum type name, e.g. "ESatoriValueChangeReasonType". + // TypePrefix is "FSatori" or "FNakama"; we replace the leading "F" with "E". + enumUEName := func(fieldType string) string { + return "E" + api.TypePrefix[1:] + fieldType + } + + getUnrealBaseType := func(fieldType string, isRepeated bool) string { + var unrealType string + + // Handle enums as their typed UE enum + if isEnum(fieldType) { + eName := enumUEName(fieldType) + if isRepeated { + return fmt.Sprintf("TArray<%s>", eName) + } + return eName + } + + if isRepeated { + switch fieldType { + case "string", + "google.protobuf.StringValue": + unrealType = "TArray" + + case "int32", "google.protobuf.Int32Value": + unrealType = "TArray" + + case "uint32", "google.protobuf.UInt32Value": + // uint32 not Blueprint-compatible, use int32 + unrealType = "TArray" + + case "int64", "google.protobuf.Int64Value": + unrealType = "TArray" + + case "uint64", "google.protobuf.UInt64Value": + unrealType = "TArray" + + case "float", "google.protobuf.FloatValue": + unrealType = "TArray" + + case "double", "google.protobuf.DoubleValue": + unrealType = "TArray" + + case "bytes", "google.protobuf.BytesValue": + unrealType = "TArray" + + case "bool", "google.protobuf.BoolValue": + unrealType = "TArray" + + default: + // For message types (starts with uppercase), use TArray + if len(fieldType) > 0 && fieldType[0] >= 'A' && fieldType[0] <= 'Z' { + unrealType = fmt.Sprintf("TArray<%s%s>", api.TypePrefix, fieldType) + } else { + unrealType = "TArray" + } + } + } else { + switch fieldType { + case "int32", "google.protobuf.Int32Value": + unrealType = "int32" + + case "uint32", "google.protobuf.UInt32Value": + // uint32 not Blueprint-compatible, use int32 + unrealType = "int32" + + case "int64", "google.protobuf.Int64Value": + unrealType = "int64" + + case "uint64", "google.protobuf.UInt64Value": + // uint64 not Blueprint-compatible, use int64 + unrealType = "int64" + + case "float", "google.protobuf.FloatValue": + unrealType = "float" + + case "double", "google.protobuf.DoubleValue": + unrealType = "double" + + case "bool", "google.protobuf.BoolValue": + unrealType = "bool" + + case "string", + "google.protobuf.StringValue", + "google.protobuf.Timestamp": + unrealType = "FString" + + case "bytes", "google.protobuf.BytesValue": + unrealType = "TArray" + + default: + // For message types (starts with uppercase), use FNakama prefix + if len(fieldType) > 0 && fieldType[0] >= 'A' && fieldType[0] <= 'Z' { + unrealType = fmt.Sprintf("%s%s", api.TypePrefix, fieldType) + } else { + unrealType = "FString" + } + } + } + + return unrealType + } + + getUnrealType := func(fieldType string, isRepeated bool) string { + baseType := getUnrealBaseType(fieldType, isRepeated) + // Don't add const& for pointers, shared pointers (Ptr suffix), or non-container types + if strings.HasSuffix(baseType, "*") || strings.HasSuffix(baseType, "Ptr") || (!strings.HasPrefix(baseType, "TArray<") && !strings.HasPrefix(baseType, "TMap<")) { + return baseType + } + return fmt.Sprintf("const %s&", baseType) + } + + getUnrealMapType := func(fieldType string) string { + var unrealType string + + switch fieldType { + case "string", + "google.protobuf.StringValue", + "google.protobuf.Timestamp": + unrealType = "TMap" + + case "int32", "int64", "uint32", "uint64", "bool", "float", "double", "google.protobuf.Int32Value", "google.protobuf.Int64Value", "google.protobuf.UInt32Value", "google.protobuf.UInt64Value": + unrealType = fmt.Sprintf("TMap", fieldType) + + default: + unrealType = "TMap" + + } + return unrealType + } + + // Helper to check if a type is a message type (needs BP wrapper) + isMessageType := func(fieldType string) bool { + switch fieldType { + case "string", "int32", "int64", "uint32", "uint64", "float", "double", "bool", + "google.protobuf.StringValue", "google.protobuf.Int32Value", "google.protobuf.Int64Value", + "google.protobuf.UInt32Value", "google.protobuf.UInt64Value", "google.protobuf.FloatValue", + "google.protobuf.DoubleValue", "google.protobuf.BoolValue", "google.protobuf.Timestamp": + return false + default: + if isEnum(fieldType) { + return false + } + return len(fieldType) > 0 && fieldType[0] >= 'A' && fieldType[0] <= 'Z' + } + } + + // Get BP type for function parameters (uses BP wrapper for message types) + getUnrealBPType := func(fieldType string, isRepeated bool) string { + if isMessageType(fieldType) { + if isRepeated { + return fmt.Sprintf("const TArray<%s%sBP>&", api.TypePrefix, fieldType) + } + return fmt.Sprintf("const %s%sBP&", api.TypePrefix, fieldType) + } + // Handle Blueprint-incompatible types + // uint32 is not supported by Blueprint - convert to int32 + if fieldType == "uint32" || fieldType == "google.protobuf.UInt32Value" { + if isRepeated { + return "const TArray&" + } + return "int32" + } + return getUnrealType(fieldType, isRepeated) + } + + getUnrealFieldType := func(fieldType string, isRepeated bool) string { + // For struct members: no const&, just TArray or T + if isRepeated { + baseType := getUnrealBaseType(fieldType, false) + return fmt.Sprintf("TArray<%s>", baseType) + } + return getUnrealBaseType(fieldType, false) + } + + getUnrealMethodCommentBlock := func(numIndent int, headerComment string, fields []*proto.NormalField, mapFields []*proto.MapField) string { + indent := strings.Repeat(" ", numIndent) + + // Extra 1 length for first line and gap + commentStrings := make([]string, 0, 1+len(fields)+len(mapFields)) + commentStrings = append(commentStrings, fmt.Sprintf("* %s\n%s*", strings.Trim(headerComment, " "), indent)) + + for _, f := range fields { + commentStrings = append(commentStrings, fmt.Sprintf("@param %s %s", textcase.PascalCase(f.Name), f.Comment.Message())) + } + for _, f := range mapFields { + commentStrings = append(commentStrings, fmt.Sprintf("@param %s %s", textcase.PascalCase(f.Name), f.Comment.Message())) + } + + comment := strings.Join(commentStrings, "\n"+indent+"* ") + return comment + } + + // isWrapperType returns true for google.protobuf.*Value types, which are optional by nature. + // StringValue and Timestamp are excluded — they use FString with IsEmpty() as sentinel. + isWrapperType := func(fieldType string) bool { + switch fieldType { + case "google.protobuf.Int32Value", "google.protobuf.Int64Value", + "google.protobuf.UInt32Value", "google.protobuf.UInt64Value", + "google.protobuf.FloatValue", "google.protobuf.DoubleValue", + "google.protobuf.BoolValue": + return true + } + return false + } + + // getUnrealOptionalType wraps wrapper proto types in TOptional<> for C++ function parameters, + // expressing true three-state optionality rather than relying on zero-value sentinels. + getUnrealOptionalType := func(fieldType string, isRepeated bool) string { + if !isRepeated && isWrapperType(fieldType) { + return fmt.Sprintf("TOptional<%s>", getUnrealBaseType(fieldType, false)) + } + return getUnrealFieldType(fieldType, isRepeated) + } + + getUnrealParamList := func(numIndent int, fields []*proto.NormalField, mapFields []*proto.MapField, oneofSlices ...[]*proto.OneOfField) string { + indent := strings.Repeat(" ", numIndent) + + params := make([]string, 0, len(fields)+len(mapFields)) + for _, f := range fields { + params = append(params, fmt.Sprintf("const %s& %s", getUnrealOptionalType(f.Type, f.Repeated), textcase.PascalCase(f.Name))) + } + for _, oneofFields := range oneofSlices { + for _, f := range oneofFields { + params = append(params, fmt.Sprintf("const %s& %s", getUnrealOptionalType(f.Type, false), textcase.PascalCase(f.Name))) + } + } + for _, f := range mapFields { + params = append(params, fmt.Sprintf("const %s& %s", getUnrealMapType(f.Type), textcase.PascalCase(f.Name))) + } + + output := strings.Join(params, ",\n"+indent) + return output + } + + // shouldFlattenMessage returns true if a message type should be flattened into + // individual function parameters rather than passed as a struct. A message is + // flattenable if it is not repeated and all its fields are primitives, enums, + // or maps (no nested messages). Repeated message fields must remain as arrays. + shouldFlattenMessage := func(fieldType string, isRepeated bool) bool { + if isRepeated { + return false + } + if !isMessageType(fieldType) { + return false + } + msg, ok := api.MessagesByName[fieldType] + if !ok { + return false + } + for _, f := range msg.Fields { + if isMessageType(f.Type) { + return false + } + } + return true + } + + // getMessageFields returns the NormalFields of a message type (for flattening). + getMessageFields := func(fieldType string) []*proto.NormalField { + msg, ok := api.MessagesByName[fieldType] + if !ok { + return nil + } + return msg.Fields + } + + // getMessageMapFields returns the MapFields of a message type (for flattening). + getMessageMapFields := func(fieldType string) []*proto.MapField { + msg, ok := api.MessagesByName[fieldType] + if !ok { + return nil + } + return msg.MapFields + } + + fnMap := template.FuncMap{ + "getUnrealType": getUnrealType, + "getUnrealMapType": getUnrealMapType, + "getUnrealBPType": getUnrealBPType, + "getUnrealMethodCommentBlock": getUnrealMethodCommentBlock, + "getUnrealParamList": getUnrealParamList, + "shouldFlattenMessage": shouldFlattenMessage, + "getMessageFields": getMessageFields, + "getMessageMapFields": getMessageMapFields, + // canDefaultFlattenedFields returns true if it's safe to add default values to + // flattened sub-fields at the given index. This is only safe when all subsequent + // NormalFields are themselves flattenable (and will also get defaults). + // MapFields are emitted in a separate template pass and always have defaults. + "canDefaultFlattenedFields": func(fields []*proto.NormalField, currentIdx int) bool { + for i := currentIdx + 1; i < len(fields); i++ { + f := fields[i] + if !shouldFlattenMessage(f.Type, f.Repeated) { + return false + } + } + return true + }, + "getUnrealReturnType": func(fieldType string) string { + return getUnrealBaseType(fieldType, false) + }, + "getUnrealFieldType": getUnrealFieldType, + "isWrapperType": isWrapperType, + // getUnrealParamDefault returns a default value suffix for flattened sub-field function parameters. + // Unlike getUnrealFieldDefault (for struct members), this also covers FString with = TEXT(""). + "getUnrealParamDefault": func(fieldType string, isRepeated bool) string { + if isRepeated { + return " = {}" + } + if isEnum(fieldType) { + return fmt.Sprintf(" = static_cast<%s>(0)", enumUEName(fieldType)) + } + switch fieldType { + case "int32", "google.protobuf.Int32Value", + "uint32", "google.protobuf.UInt32Value": + return " = 0" + case "int64", "google.protobuf.Int64Value", + "uint64", "google.protobuf.UInt64Value": + return " = 0" + case "float", "google.protobuf.FloatValue": + return " = 0.0f" + case "double", "google.protobuf.DoubleValue": + return " = 0.0" + case "bool", "google.protobuf.BoolValue": + return " = false" + case "string", "google.protobuf.StringValue", "google.protobuf.Timestamp": + return " = TEXT(\"\")" + default: + return " = {}" + } + }, + "getUnrealFieldDefault": func(fieldType string, isRepeated bool) string { + // Return a default initializer for primitive UPROPERTY fields. + // UE requires all UPROPERTY primitives to be initialized. + if isRepeated { + return "" // TArray, TMap etc. are default-constructed + } + if isEnum(fieldType) { + return fmt.Sprintf(" = static_cast<%s>(0)", enumUEName(fieldType)) + } + switch fieldType { + case "int32", "google.protobuf.Int32Value", + "uint32", "google.protobuf.UInt32Value": + return " = 0" + case "int64", "google.protobuf.Int64Value", + "uint64", "google.protobuf.UInt64Value": + return " = 0" + case "float", "google.protobuf.FloatValue": + return " = 0.0f" + case "double", "google.protobuf.DoubleValue": + return " = 0.0" + case "bool", "google.protobuf.BoolValue": + return " = false" + default: + return "" // FString, FNakama* structs are default-constructed + } + }, + "ueReservedParamNamePostfix": func(name string) string { + if _, reserved := ueParamKeywords[name]; reserved { + return name + "_" + } + return name + }, + "enumUEName": enumUEName, + "isMessageType": isMessageType, + "isBPWrapperType": isMessageType, // Alias for template clarity + "getBaseType": func(fieldType string) string { + // Return the base type name (e.g., "User" from "User") + return fieldType + }, + "getUnrealBPFieldType": func(fieldType string, isRepeated bool) string { + // For BP wrapper struct fields: use FNakama*BP for message types + if isMessageType(fieldType) { + if isRepeated { + return fmt.Sprintf("TArray<%s%sBP>", api.TypePrefix, fieldType) + } + return fmt.Sprintf("%s%sBP", api.TypePrefix, fieldType) + } + // For primitives, handle Blueprint-incompatible types + // uint32 is not supported by Blueprint - convert to int32 + if fieldType == "uint32" || fieldType == "google.protobuf.UInt32Value" { + if isRepeated { + return "TArray" + } + return "int32" + } + // For other primitives, use same as native + if isRepeated { + baseType := getUnrealBaseType(fieldType, false) + return fmt.Sprintf("TArray<%s>", baseType) + } + return getUnrealBaseType(fieldType, false) + }, + "getUnrealDelegateParamType": func(fieldType string) string { + baseType := getUnrealBaseType(fieldType, false) + // Don't add const& for pointers or shared pointers (Ptr suffix) + if strings.HasSuffix(baseType, "*") || strings.HasSuffix(baseType, "Ptr") { + return baseType + } + return fmt.Sprintf("const %s&", baseType) + }, + "asyncResultType": func(returnType *visitedMessage) string { + if returnType == nil { + return api.TypePrefix + "Void" + } + return fmt.Sprintf("%s%s", api.TypePrefix, returnType.Name) + }, + "futureResultType": func(returnType *visitedMessage) string { + if returnType == nil { + return "FVoidResult" + } + return fmt.Sprintf("F%sResult", returnType.Name) + }, + + // TODO: Implement these. + "getUnrealTypeEmptyPredicate": func(objName string, objType string) string { + return fmt.Sprintf("%s.IsSet()", textcase.PascalCase(objName)) + }, + "getUnrealToStringConversion": func(objName string, objType string) string { + return fmt.Sprintf("FString::FromInt(%s)", textcase.PascalCase(objName)) + }, + } + + return fnMap +} + +func mergeFuncMaps(funcMaps ...template.FuncMap) template.FuncMap { + result := template.FuncMap{} + + for _, m := range funcMaps { + maps.Copy(result, m) + } + return result +} + +// isPrimitiveOrWrapperType returns true if the type is a primitive, wrapper, or google type +func isPrimitiveOrWrapperType(fieldType string) bool { + switch fieldType { + case "string", "int32", "int64", "uint32", "uint64", "float", "double", "bool", "bytes", + "google.protobuf.StringValue", "google.protobuf.Int32Value", "google.protobuf.Int64Value", + "google.protobuf.UInt32Value", "google.protobuf.UInt64Value", "google.protobuf.FloatValue", + "google.protobuf.DoubleValue", "google.protobuf.BoolValue", "google.protobuf.Timestamp", + "google.protobuf.Struct", "google.protobuf.Empty": + return true + } + return false +} + +// ueParamKeywords lists names that UHT rejects as UFUNCTION parameter names. +var ueParamKeywords = map[string]struct{}{ + "Self": {}, +} + +var cppKeywords = map[string]struct{}{ + "alignas": {}, + "alignof": {}, + "and": {}, + "and_eq": {}, + "asm": {}, + "atomic_cancel": {}, + "atomic_commit": {}, + "atomic_noexcept": {}, + "auto": {}, + "bitand": {}, + "bitor": {}, + "bool": {}, + "break": {}, + "case": {}, + "catch": {}, + "char": {}, + "char8_t": {}, + "char16_t": {}, + "char32_t": {}, + "class": {}, + "compl": {}, + "concept": {}, + "const": {}, + "consteval": {}, + "constexpr": {}, + "constinit": {}, + "const_cast": {}, + "continue": {}, + "contract_assert": {}, + "co_await": {}, + "co_return": {}, + "co_yield": {}, + "decltype": {}, + "default": {}, + "delete": {}, + "do": {}, + "double": {}, + "dynamic_cast": {}, + "else": {}, + "enum": {}, + "explicit": {}, + "export": {}, + "extern": {}, + "false": {}, + "float": {}, + "for": {}, + "friend": {}, + "goto": {}, + "if": {}, + "inline": {}, + "int": {}, + "long": {}, + "mutable": {}, + "namespace": {}, + "new": {}, + "noexcept": {}, + "not": {}, + "not_eq": {}, + "nullptr": {}, + "operator": {}, + "or": {}, + "or_eq": {}, + "private": {}, + "protected": {}, + "public": {}, + "reflexpr": {}, + "register": {}, + "reinterpret_cast": {}, + "requires": {}, + "return": {}, + "short": {}, + "signed": {}, + "sizeof": {}, + "static": {}, + "static_assert": {}, + "static_cast": {}, + "struct": {}, + "switch": {}, + "synchronized": {}, + "template": {}, + "this": {}, + "thread_local": {}, + "throw": {}, + "true": {}, + "try": {}, + "typedef": {}, + "typeid": {}, + "typename": {}, + "union": {}, + "unsigned": {}, + "using": {}, + "virtual": {}, + "void": {}, + "volatile": {}, + "wchar_t": {}, + "while": {}, + "xor": {}, + "xor_eq": {}, +} diff --git a/cmd/codegen/templates/_errorcodes.ue.h.tmpl b/cmd/codegen/templates/_errorcodes.ue.h.tmpl new file mode 100644 index 000000000..dcfe0787b --- /dev/null +++ b/cmd/codegen/templates/_errorcodes.ue.h.tmpl @@ -0,0 +1,23 @@ +{{- define "errorcodes" }} +/** gRPC status codes returned by {{ . }} in F{{ . }}Error::Code. */ +namespace E{{ . }}ErrorCode +{ + constexpr int32 OK = 0; + constexpr int32 Cancelled = 1; + constexpr int32 Unknown = 2; + constexpr int32 InvalidArgument = 3; + constexpr int32 DeadlineExceeded = 4; + constexpr int32 NotFound = 5; + constexpr int32 AlreadyExists = 6; + constexpr int32 PermissionDenied = 7; + constexpr int32 ResourceExhausted = 8; + constexpr int32 FailedPrecondition = 9; + constexpr int32 Aborted = 10; + constexpr int32 OutOfRange = 11; + constexpr int32 Unimplemented = 12; + constexpr int32 Internal = 13; + constexpr int32 Unavailable = 14; + constexpr int32 DataLoss = 15; + constexpr int32 Unauthenticated = 16; +} +{{- end }} diff --git a/cmd/codegen/templates/_serializejson.ue.cpp.tmpl b/cmd/codegen/templates/_serializejson.ue.cpp.tmpl new file mode 100644 index 000000000..6394d6d09 --- /dev/null +++ b/cmd/codegen/templates/_serializejson.ue.cpp.tmpl @@ -0,0 +1,29 @@ +{{- define "serializejson" }} +FString SerializeJsonToString(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(1024); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + return FString(Converter.Length(), Converter.Get()); +} + +FString SerializeJsonEscaped(const TSharedPtr& Json) +{ + TArray JsonBytes; + JsonBytes.Reserve(2048); + FMemoryWriter Archive(JsonBytes); + TSharedRef>> Writer = + TJsonWriterFactory>::Create(&Archive); + FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); + Writer->Close(); + FUTF8ToTCHAR Converter(reinterpret_cast(JsonBytes.GetData()), JsonBytes.Num()); + FString Condensed(Converter.Length(), Converter.Get()); + FString Escaped = Condensed.Replace(TEXT("\\"), TEXT("\\\\")).Replace(TEXT("\""), TEXT("\\\"")); + return TEXT("\"") + Escaped + TEXT("\""); +} +{{- end }} diff --git a/cmd/codegen/templates/nakama-api.ue.cpp.tmpl b/cmd/codegen/templates/nakama-api.ue.cpp.tmpl new file mode 100644 index 000000000..6633caa28 --- /dev/null +++ b/cmd/codegen/templates/nakama-api.ue.cpp.tmpl @@ -0,0 +1,515 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaApi.h" +#include "NakamaHttpHelper.h" + +#include "Modules/ModuleManager.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +DEFINE_LOG_CATEGORY(LogNakama); + +using namespace NakamaHttpInternal; + +{{- /* Generate free function implementations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +void NakamaApi::{{ $rpc.Name }}( + const FNakamaClientConfig& Config, + {{- if $needsSession }} + const FNakamaSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("{{ $rpc.Endpoint }}"); + {{- /* Handle path parameters like {group_id} */ -}} + {{- range $pidx, $param := $rpc.QueryParams }} + {{- if ne $param "" }} + { const FString Encoded_{{ $param | pascalCase }} = FGenericPlatformHttp::UrlEncode({{ $param | pascalCase }}); Endpoint = Endpoint.Replace(TEXT("{{ printf "{%s}" $param }}"), *Encoded_{{ $param | pascalCase }}); } + {{- end }} + {{- end }} + +{{- /* Handle query parameters (fields not in body) */ -}} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- $pathParams := $rpc.QueryParams }} + TArray QueryParams; +{{- range $fidx, $field := $requestType.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- $isPathParam := containsInSlice $pathParams $jsonFieldName }} +{{- $isQueryParam := and (not $isPathParam) (or $isGetRequest (eq $bodyField "") (and (ne $bodyField "*") (ne $jsonFieldName $bodyField))) }} +{{- if $isQueryParam }} +{{- if $field.Repeated }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + for (const FString& Item : {{ $fieldName }}) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode(Item)); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + for (const int32& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), Item)); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + for (const int64& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), Item)); + } +{{- end }} +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode({{ $fieldName }})); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%s"), {{ $fieldName }} ? TEXT("true") : TEXT("false"))); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), {{ $fieldName }})); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), {{ $fieldName }})); + } +{{- end }} +{{- end }} +{{- end }} +{{- end }} + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } +{{- end }} + + ENakamaRequestAuth AuthType = {{ if or $isAuthenticate $isSessionRefresh }}ENakamaRequestAuth::Basic{{ else }}ENakamaRequestAuth::Bearer{{ end }}; + +{{- /* Build request body and dispatch */ -}} +{{- $scalarBodyField := "" }} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if eq $field.Name $bodyField }} +{{- if not (isMessageType $field.Type) }} +{{- $scalarBodyField = $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- if ne $scalarBodyField "" }} + + FString BodyString; + if ({{ $scalarBodyField }}.IsValid()) + { + BodyString = SerializeJsonEscaped({{ $scalarBodyField }}); + } + + SendRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), BodyString, AuthType, {{ if $needsSession }}Session.Token{{ else }}TEXT(""){{ end }}, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FNakama{{ $returnType.Name }} Result = FNakama{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- else }} +{{- /* Object body or no body */ -}} + TSharedPtr Body; +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if eq $field.Name $bodyField }} +{{- $fieldName := $field.Name | pascalCase }} + Body = {{ $fieldName }}.ToJson(); +{{- end }} +{{- end }} +{{- else if and (eq $bodyField "*") (not $isGetRequest) }} + Body = MakeShared(); +{{- range $fidx, $field := $requestType.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Body->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Body->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if isEnumType $fieldType }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else if isMessageType $fieldType }} + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- else }} + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } +{{- end }} +{{- end }} +{{- end }} + + MakeRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), Body, AuthType, {{ if $needsSession }}Session.Token{{ else }}TEXT(""){{ end }}, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FNakama{{ $returnType.Name }} Result = FNakama{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- end }} +} +{{- if $needsSession }} + +void NakamaApi::{{ $rpc.Name }}( + const FNakamaClientConfig& Config, + const FString& HttpKey, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- /* Skip http_key field since it's provided as the HttpKey auth parameter */ -}} + {{- if ne $field.Name "http_key" }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("{{ $rpc.Endpoint }}"); + {{- /* Handle path parameters like {group_id} */ -}} + {{- range $pidx, $param := $rpc.QueryParams }} + {{- if ne $param "" }} + { const FString Encoded_{{ $param | pascalCase }} = FGenericPlatformHttp::UrlEncode({{ $param | pascalCase }}); Endpoint = Endpoint.Replace(TEXT("{{ printf "{%s}" $param }}"), *Encoded_{{ $param | pascalCase }}); } + {{- end }} + {{- end }} + +{{- /* Handle query parameters (fields not in body) */ -}} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- $pathParams := $rpc.QueryParams }} + TArray QueryParams; +{{- range $fidx, $field := $requestType.Fields }} +{{- /* Skip http_key field since it's provided as the HttpKey auth parameter */ -}} +{{- if ne $field.Name "http_key" }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- $isPathParam := containsInSlice $pathParams $jsonFieldName }} +{{- $isQueryParam := and (not $isPathParam) (or $isGetRequest (eq $bodyField "") (and (ne $bodyField "*") (ne $jsonFieldName $bodyField))) }} +{{- if $isQueryParam }} +{{- if $field.Repeated }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + for (const FString& Item : {{ $fieldName }}) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode(Item)); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + for (const int32& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), Item)); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + for (const int64& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), Item)); + } +{{- end }} +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode({{ $fieldName }})); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%s"), {{ $fieldName }} ? TEXT("true") : TEXT("false"))); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), {{ $fieldName }})); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), {{ $fieldName }})); + } +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } +{{- end }} + +{{- /* Build request body and dispatch */ -}} +{{- $scalarBodyField2 := "" }} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if and (eq $field.Name $bodyField) (ne $field.Name "http_key") }} +{{- if not (isMessageType $field.Type) }} +{{- $scalarBodyField2 = $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- if ne $scalarBodyField2 "" }} + + FString BodyString; + if ({{ $scalarBodyField2 }}.IsValid()) + { + BodyString = SerializeJsonEscaped({{ $scalarBodyField2 }}); + } + + SendRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), BodyString, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FNakama{{ $returnType.Name }} Result = FNakama{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- else }} +{{- /* Object body or no body */ -}} + TSharedPtr Body; +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if and (eq $field.Name $bodyField) (ne $field.Name "http_key") }} +{{- $fieldName := $field.Name | pascalCase }} + Body = {{ $fieldName }}.ToJson(); +{{- end }} +{{- end }} +{{- else if and (eq $bodyField "*") (not $isGetRequest) }} + Body = MakeShared(); +{{- range $fidx, $field := $requestType.Fields }} +{{- /* Skip http_key field */ -}} +{{- if ne $field.Name "http_key" }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Body->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Body->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if isEnumType $fieldType }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else if isMessageType $fieldType }} + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- else }} + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } +{{- end }} +{{- end }} +{{- end }} + + MakeRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), Body, ENakamaRequestAuth::HttpKey, HttpKey, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FNakama{{ $returnType.Name }} Result = FNakama{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- end }} +} +{{- end }} +{{- end }} + +// Module implementation +class FNakamaApiModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogNakama, Log, TEXT("NakamaApi module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogNakama, Log, TEXT("NakamaApi module shutting down")); + } +}; + +IMPLEMENT_MODULE(FNakamaApiModule, NakamaApi) diff --git a/cmd/codegen/templates/nakama-api.ue.h.tmpl b/cmd/codegen/templates/nakama-api.ue.h.tmpl new file mode 100644 index 000000000..02ae8363a --- /dev/null +++ b/cmd/codegen/templates/nakama-api.ue.h.tmpl @@ -0,0 +1,102 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" +#include "NakamaTypes.h" + +NAKAMAAPI_API DECLARE_LOG_CATEGORY_EXTERN(LogNakama, Log, All); + +/** Low-level Nakama API: data types + free functions for HTTP RPCs (callback-based). */ +namespace NakamaApi +{ + +{{- /* Generate free function declarations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + + /** {{ $rpc.Comment }} */ + NAKAMAAPI_API void {{ $rpc.Name }}( + const FNakamaClientConfig& Config, + {{- if $needsSession }} + const FNakamaSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- if $needsSession }} + + /** {{ $rpc.Comment }} (Server-to-server with HTTP key) */ + NAKAMAAPI_API void {{ $rpc.Name }}( + const FNakamaClientConfig& Config, + const FString& HttpKey, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- /* Skip http_key field since it's provided as the HttpKey auth parameter */ -}} + {{- if ne $field.Name "http_key" }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +{{- end }} +} diff --git a/cmd/codegen/templates/nakama-client-bplib.ue.cpp.tmpl b/cmd/codegen/templates/nakama-client-bplib.ue.cpp.tmpl new file mode 100644 index 000000000..63350a8f4 --- /dev/null +++ b/cmd/codegen/templates/nakama-client-bplib.ue.cpp.tmpl @@ -0,0 +1,168 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +// {{ $rpc.Name }} +UNakamaClient{{ $rpc.Name }}* UNakamaClient{{ $rpc.Name }}::{{ $rpc.Name }}( + UObject* WorldContextObject, + FNakamaClientConfig Client +{{- if $needsSession }}, + const FNakamaSession& Session +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }}, +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sfidx, $sf := getMessageFields $field.Type }} +{{- if $sfidx }},{{- end }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }} +{{- end }} +{{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + const TMap& {{ $field.Name | pascalCase }} +{{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }} +{{- end }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sf := getMessageMapFields $field.Type }}, + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }} +{{- end }} +{{- end }} +{{- end }} +{{- end }}) +{ + UNakamaClient{{ $rpc.Name }}* Action = NewObject(GetTransientPackage()); + Action->Client = Client; +{{- if $needsSession }} + Action->Session = Session; +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sf := getMessageFields $field.Type }} + Action->Stored{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; +{{- end }} +{{- range $sf := getMessageMapFields $field.Type }} + Action->Stored{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; +{{- end }} +{{- else }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase }}; +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase }}; +{{- end }} +{{- end }} + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaClient{{ $rpc.Name }}::Activate() +{ + static const TCHAR* TraceScope_{{ $rpc.Name }} = TEXT("NakamaBP_{{ $rpc.Name }}"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_{{ $rpc.Name }}); + + TWeakObjectPtr WeakThis(this); + + {{- /* Reconstruct structs from flattened stored params for the NakamaApi call */ -}} + {{- if $hasParams }} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{ getUnrealReturnType $field.Type }} Stored{{ $field.Name | pascalCase }}; + {{- range $sf := getMessageFields $field.Type }} + Stored{{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = Stored{{ $sf.Name | pascalCase }}; + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + Stored{{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = Stored{{ $sf.Name | pascalCase }}; + {{- end }} + {{- end }} + {{- end }} + {{- end }} + + NakamaApi::{{ $rpc.Name }}( + Client, +{{- if $needsSession }} + Session, +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + [&]() -> TSharedPtr { + if (Stored{{ $field.Name | pascalCase }}.Num() == 0) { return nullptr; } + TSharedPtr Json = MakeShared(); + for (const auto& Pair : Stored{{ $field.Name | pascalCase }}) + { + Json->SetStringField(Pair.Key, Pair.Value); + } + return Json; + }(), +{{- else }} + Stored{{ $field.Name | pascalCase }}, +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + Stored{{ $field.Name | pascalCase }}, +{{- end }} +{{- end }} +{{- if $hasReturn }} + [WeakThis](const FNakama{{ $returnType.Name }}& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, +{{- else }} + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, +{{- end }} + [WeakThis](const FNakamaError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} +{{- end }} diff --git a/cmd/codegen/templates/nakama-client-bplib.ue.h.tmpl b/cmd/codegen/templates/nakama-client-bplib.ue.h.tmpl new file mode 100644 index 000000000..c1898426f --- /dev/null +++ b/cmd/codegen/templates/nakama-client-bplib.ue.h.tmpl @@ -0,0 +1,134 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "NakamaTypes.h" +#include "NakamaApi.h" + +#include "NakamaClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaError, const FNakamaError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNakamaSuccess); + +{{- /* Generate success delegates for each unique return type */ -}} +{{- range $idx, $typeName := uniqueReturnTypes $.Rpcs }} +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakama{{ $typeName }}, const FNakama{{ $typeName }}&, Result); +{{- end }} + +// ============================================================================ +// Async Action Classes (one per RPC) +// ============================================================================ +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +/** + * {{ $rpc.Comment }} + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaClient{{ $rpc.Name }} : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: +{{- if $hasReturn }} + UPROPERTY(BlueprintAssignable) + FOnNakama{{ $returnType.Name }} OnSuccess; +{{- else }} + UPROPERTY(BlueprintAssignable) + FOnNakamaSuccess OnSuccess; +{{- end }} + + UPROPERTY(BlueprintAssignable) + FOnNakamaError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "{{ $rpc.Name }}"), Category = "Nakama|Client") + static UNakamaClient{{ $rpc.Name }}* {{ $rpc.Name }}( + UObject* WorldContextObject, + FNakamaClientConfig Client +{{- if $needsSession }}, + const FNakamaSession& Session +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }}, +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sfidx, $sf := getMessageFields $field.Type }} +{{- if $sfidx }},{{- end }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }} +{{- end }} +{{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + const TMap& {{ $field.Name | pascalCase }} +{{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }} +{{- end }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sf := getMessageMapFields $field.Type }}, + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }} +{{- end }} +{{- end }} +{{- end }} +{{- end }}); + + virtual void Activate() override; + +private: + FNakamaClientConfig Client; + +{{- if $needsSession }} + FNakamaSession Session; +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if shouldFlattenMessage $field.Type $field.Repeated }} +{{- range $sf := getMessageFields $field.Type }} + {{ getUnrealFieldType $sf.Type $sf.Repeated }} Stored{{ $sf.Name | pascalCase }}{{ getUnrealFieldDefault $sf.Type $sf.Repeated }}; +{{- end }} +{{- range $sf := getMessageMapFields $field.Type }} + {{ getUnrealMapType $sf.Type }} Stored{{ $sf.Name | pascalCase }}; +{{- end }} +{{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TMap Stored{{ $field.Name | pascalCase }}; +{{- else }} + {{ getUnrealFieldType $field.Type $field.Repeated }} Stored{{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + {{ getUnrealMapType $field.Type }} Stored{{ $field.Name | pascalCase }}; +{{- end }} +{{- end }} +}; +{{- end }} diff --git a/cmd/codegen/templates/nakama-rtclient-bplib.ue.cpp.tmpl b/cmd/codegen/templates/nakama-rtclient-bplib.ue.cpp.tmpl new file mode 100644 index 000000000..d7aed052d --- /dev/null +++ b/cmd/codegen/templates/nakama-rtclient-bplib.ue.cpp.tmpl @@ -0,0 +1,171 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ +{{- $envelope := getMessage "Envelope" -}} +{{- range $idx, $rpc := $envelope.OneofFields -}} +{{- $msgType := getMessage (stripDot $rpc.Type) }} + +// {{ $msgType.Name }} +UNakamaRealtimeClient{{ $msgType.Name }}* UNakamaRealtimeClient{{ $msgType.Name }}::{{ $msgType.Name }}( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem +{{- range $fidx, $field := $msgType.Fields }}, + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase | ueReservedParamNamePostfix }} +{{- end }} +{{- range $fidx, $field := $msgType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase | ueReservedParamNamePostfix }} +{{- end }}) +{ + UNakamaRealtimeClient{{ $msgType.Name }}* Action = NewObject(GetTransientPackage()); + Action->StoredWebSocketSubsystem = WebSocketSubsystem; +{{- range $fidx, $field := $msgType.Fields }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase | ueReservedParamNamePostfix }}; +{{- end }} +{{- range $fidx, $field := $msgType.MapFields }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase | ueReservedParamNamePostfix }}; +{{- end }} + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void UNakamaRealtimeClient{{ $msgType.Name }}::Activate() +{ + static const TCHAR* TraceScope_{{ $msgType.Name }} = TEXT("NakamaRTBP_{{ $msgType.Name }}"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_{{ $msgType.Name }}); + + if (!StoredWebSocketSubsystem) + { + FNakamaRtError Err; + Err.Message = TEXT("WebSocket subsystem is null"); + OnError.Broadcast(Err); + SetReadyToDestroy(); + return; + } + + TWeakObjectPtr WeakThis(this); + + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msgType.Fields }} +{{- $fieldPascal := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Stored{{ $fieldPascal }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : Stored{{ $fieldPascal }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!Stored{{ $fieldPascal }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}); +{{- else if or (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + if (Stored{{ $fieldPascal }} != 0) + { + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}); +{{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), FBase64::Encode(Stored{{ $fieldPascal }})); +{{- else if containsString $fieldType "Timestamp" }} + if (!Stored{{ $fieldPascal }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast(Stored{{ $fieldPascal }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), Stored{{ $fieldPascal }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msgType.MapFields }} +{{- $fieldPascal := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} + if (Stored{{ $fieldPascal }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : Stored{{ $fieldPascal }}) + { +{{- if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + MapObj->SetNumberField(Pair.Key, Pair.Value); +{{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + MapObj->SetObjectField(Pair.Key, Pair.Value); +{{- else }} + MapObj->SetStringField(Pair.Key, Pair.Value); +{{- end }} + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } +{{- end }} + + StoredWebSocketSubsystem->Send(TEXT("{{ $rpc.Name }}"), Json) + .Next([WeakThis](FNakamaWebSocketResponse Resp) + { + auto* Self = WeakThis.Get(); + if (!Self) + { + return; + } + + if (Resp.ErrorCode == ENakamaWebSocketError::None) + { + Self->OnSuccess.Broadcast(); + } + else + { + FNakamaRtError Err; + if (Resp.Data.IsValid()) + { + Err = FNakamaRtError::FromJson(Resp.Data); + } + else + { + Err.Code = static_cast(Resp.ErrorCode); + } + Self->OnError.Broadcast(Err); + } + Self->SetReadyToDestroy(); + }); +} +{{- end }} diff --git a/cmd/codegen/templates/nakama-rtclient-bplib.ue.h.tmpl b/cmd/codegen/templates/nakama-rtclient-bplib.ue.h.tmpl new file mode 100644 index 000000000..110cb3d85 --- /dev/null +++ b/cmd/codegen/templates/nakama-rtclient-bplib.ue.h.tmpl @@ -0,0 +1,80 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "NakamaRtTypes.h" +#include "NakamaWebSocketSubsystem.h" + +#include "NakamaRtClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNakamaRtError, const FNakamaRtError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNakamaRtSuccess); + +// ============================================================================ +// Async Action Classes (one per RT operation) +// ============================================================================ +{{- $envelope := getMessage "Envelope" -}} +{{- range $idx, $rpc := $envelope.OneofFields -}} +{{- $msgType := getMessage (stripDot $rpc.Type) }} + +/** + * {{ $msgType.Comment }} + */ +UCLASS(Transient) +class NAKAMABLUEPRINTS_API UNakamaRealtimeClient{{ $msgType.Name }} : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FOnNakamaRtSuccess OnSuccess; + + UPROPERTY(BlueprintAssignable) + FOnNakamaRtError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "{{ $msgType.Name }}"), Category = "Nakama|Realtime Client") + static UNakamaRealtimeClient{{ $msgType.Name }}* {{ $msgType.Name }}( + UObject* WorldContextObject, + UNakamaWebSocketSubsystem* WebSocketSubsystem +{{- range $fidx, $field := $msgType.Fields }}, + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase | ueReservedParamNamePostfix }} +{{- end }} +{{- range $fidx, $field := $msgType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase | ueReservedParamNamePostfix }} +{{- end }}); + + virtual void Activate() override; + +private: + UPROPERTY() + UNakamaWebSocketSubsystem* StoredWebSocketSubsystem = nullptr; +{{- range $fidx, $field := $msgType.Fields }} + {{ getUnrealFieldType $field.Type $field.Repeated }} Stored{{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; +{{- end }} +{{- range $fidx, $field := $msgType.MapFields }} + {{ getUnrealMapType $field.Type }} Stored{{ $field.Name | pascalCase }}; +{{- end }} +}; +{{- end }} diff --git a/cmd/codegen/templates/nakama-rtclient.ue.cpp.tmpl b/cmd/codegen/templates/nakama-rtclient.ue.cpp.tmpl new file mode 100644 index 000000000..0c5856c05 --- /dev/null +++ b/cmd/codegen/templates/nakama-rtclient.ue.cpp.tmpl @@ -0,0 +1,140 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRt.h" +#include "NakamaApi.h" + +{{ $envelope := getMessage "Envelope" }} + +namespace Nakama +{ +TNakamaFuture NakamaRealtimeClient::Connect( + const FNakamaWebSocketConnectionParams& Params +) noexcept +{ + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Connect(Params); +} + +{{- range $idx, $rpc := $envelope.OneofFields -}} +{{- $rpcType := getMessage (stripDot $rpc.Type) }} +TNakamaFuture NakamaRealtimeClient::{{ $rpcType.Name | pascalCase }}( + {{ getUnrealParamList 4 $rpcType.Fields $rpcType.MapFields $rpcType.OneofFields }} +) noexcept +{ + TSharedPtr Json = MakeShared(); + {{- range $fidx, $field := $rpcType.OneofFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } + {{- end }} + {{- range $fidx, $field := $rpcType.Fields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + {{- $fieldType := $field.Type }} + {{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { + {{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); + {{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); + {{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); + {{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + Array.Add(MakeShared(FBase64::Encode(Item)); + {{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); + {{- else }} + Array.Add(MakeShared(Item)); + {{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } + {{- else }} + {{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } + {{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + {{- else if or (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + if ({{ $fieldName }}.IsSet()) + { + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.GetValue()); + } + {{- else if eq $fieldType "bool" }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + {{- else if containsString $fieldType "BoolValue" }} + if ({{ $fieldName }}.IsSet()) + { + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.GetValue()); + } + {{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), FBase64::Encode({{ $fieldName }})); + {{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } + {{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); + {{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); + {{- end }} + {{- end }} + {{- end }} + {{- range $fidx, $field := $rpcType.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $fieldType := $field.Type }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + {{- if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + MapObj->SetNumberField(Pair.Key, Pair.Value); + {{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + MapObj->SetObjectField(Pair.Key, Pair.Value); + {{- else -}} + MapObj->SetStringField(Pair.Key, Pair.Value); + {{- end -}} + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + + if (!WebSocketSubsystem.IsValid()) + { + return MakeCompletedFuture({ .ErrorCode = ENakamaWebSocketError::ConnectionFailed }); + } + return WebSocketSubsystem->Send(TEXT("{{ $rpc.Name }}"), Json); +} +{{ end }} +} diff --git a/cmd/codegen/templates/nakama-rtclient.ue.h.tmpl b/cmd/codegen/templates/nakama-rtclient.ue.h.tmpl new file mode 100644 index 000000000..95b294928 --- /dev/null +++ b/cmd/codegen/templates/nakama-rtclient.ue.h.tmpl @@ -0,0 +1,75 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaWebSocketSubsystem.h" +#include "NakamaRtTypes.h" + +namespace Nakama +{ + class NakamaRealtimeClient + { + private: + TWeakObjectPtr WebSocketSubsystem; + + public: + explicit NakamaRealtimeClient(UGameInstance* InGi) + { + if (InGi == nullptr) + { + UE_LOG(LogNakama, Error, TEXT("NakamaRealtimeClient constructor received null GameInstance pointer.")); + return; + } + WebSocketSubsystem = InGi->GetSubsystem(); + } + ~NakamaRealtimeClient() + { + if (WebSocketSubsystem.IsValid()) + { + WebSocketSubsystem->Close(); + } + } + + NakamaRealtimeClient(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient& operator=(const NakamaRealtimeClient&) = delete; + NakamaRealtimeClient(NakamaRealtimeClient&&) = delete; + NakamaRealtimeClient& operator=(NakamaRealtimeClient&&) = delete; + + /** Connect (or reconnect) the WebSocket. Returns a future that resolves once the + * handshake completes or fails. */ + NAKAMA_API TNakamaFuture Connect( + const FNakamaWebSocketConnectionParams& Params + ) noexcept; + + {{- "\n" -}} + + {{- $envelope := getMessage "Envelope" -}} + {{- range $idx, $rpc := $envelope.OneofFields -}} + {{- $rpcType := getMessage (stripDot $rpc.Type) }} + /** + {{ getUnrealMethodCommentBlock 8 $rpc.Comment.Message $rpcType.Fields $rpcType.MapFields }} + */ + NAKAMA_API TNakamaFuture {{ $rpcType.Name | pascalCase }}( + {{ getUnrealParamList 12 $rpcType.Fields $rpcType.MapFields $rpcType.OneofFields }} + ) noexcept; + {{ end }} + }; +} + diff --git a/cmd/codegen/templates/nakama-rttypes.ue.cpp.tmpl b/cmd/codegen/templates/nakama-rttypes.ue.cpp.tmpl new file mode 100644 index 000000000..4ce6f330c --- /dev/null +++ b/cmd/codegen/templates/nakama-rttypes.ue.cpp.tmpl @@ -0,0 +1,237 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaRtTypes.h" + +{{- /* Generate FromJson/ToJson implementations for each message */ -}} +{{- range $idx, $msg := $.Messages }} + +FNakamaRt{{ $msg.Name }} FNakamaRt{{ $msg.Name }}::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaRt{{ $msg.Name }} Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ $jsonFieldName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + Result.{{ $fieldName }}.Add(Item->AsNumber()); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Result.{{ $fieldName }}.Add(Item->AsBool()); +{{- else if isMessageType $fieldType }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ $fieldName }}.Add(FNakamaRt{{ $fieldType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- end }} + } + } + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetIntegerField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetBoolField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetNumberField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + FString Base64 = Json->GetStringField(TEXT("data")); + if (!FBase64::Decode(Base64, Result.Data)) + { + UE_LOG(LogNakama, Warning, TEXT("Couldn't parse bytes: %s"), *Base64); + } + } +{{- else if containsString $fieldType "Timestamp" }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if isEnumType $fieldType }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast<{{ enumUEName $fieldType }}>(Json->GetIntegerField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), NestedObj)) + { + Result.{{ $fieldName }} = FNakamaRt{{ $fieldType }}::FromJson(*NestedObj); + } + } +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + {{ if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsNumber()); + {{ else }} + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsString()); + {{ end }} + } + } + } + {{- end }} + return Result; +} + +TSharedPtr FNakamaRt{{ $msg.Name }}::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + if ({{ $fieldName }} != 0) + { + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "bytes") (containsString $fieldType "BytesValue") }} + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), FBase64::Encode("{{ $fieldName }}")); +{{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $fieldType := $field.Type }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + {{ if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + MapObj->SetNumberField(Pair.Key, Pair.Value); + {{ else }} + MapObj->SetStringField(Pair.Key, Pair.Value); + {{ end }} + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + return Json; +} +{{- end }} + + diff --git a/cmd/codegen/templates/nakama-rttypes.ue.h.tmpl b/cmd/codegen/templates/nakama-rttypes.ue.h.tmpl new file mode 100644 index 000000000..c00585568 --- /dev/null +++ b/cmd/codegen/templates/nakama-rttypes.ue.h.tmpl @@ -0,0 +1,54 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" + +#include "NakamaRtTypes.generated.h" + +// Forward declarations +{{- range $idx, $msg := $.Messages }} +struct FNakamaRt{{ $msg.Name }}; +{{- end }} + +{{- /* Generate struct definitions for each proto message */ -}} +{{- range $idx, $msg := $.Messages }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaRt{{ $msg.Name }} +{ + GENERATED_BODY() + + {{- range $fidx, $field := $msg.Fields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealFieldType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; + {{- end }} + {{- range $fidx, $field := $msg.MapFields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealMapType $field.Type }} {{ $field.Name | pascalCase }}; + {{- end }} + + static FNakamaRt{{ $msg.Name }} FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; +{{- end }} + diff --git a/cmd/codegen/templates/nakama-types.ue.cpp.tmpl b/cmd/codegen/templates/nakama-types.ue.cpp.tmpl new file mode 100644 index 000000000..1e2b6668a --- /dev/null +++ b/cmd/codegen/templates/nakama-types.ue.cpp.tmpl @@ -0,0 +1,521 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "NakamaTypes.h" + +// --- FNakamaSession JWT helpers --- + +bool FNakamaSession::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + // Base64url -> standard Base64 + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + // Pad to multiple of 4 + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void FNakamaSession::ParseTokens() noexcept +{ + UserId.Empty(); + Username.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + Vars.Empty(); + + // Parse auth token + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("uid"))) + { + UserId = TokenPayload->GetStringField(TEXT("uid")); + } + if (TokenPayload->HasField(TEXT("usn"))) + { + Username = TokenPayload->GetStringField(TEXT("usn")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + if (TokenPayload->HasField(TEXT("vrs"))) + { + const TSharedPtr* VrsObj; + if (TokenPayload->TryGetObjectField(TEXT("vrs"), VrsObj)) + { + for (const auto& Pair : (*VrsObj)->Values) + { + Vars.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + } + + // Parse refresh token + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool FNakamaSession::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool FNakamaSession::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void FNakamaSession::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} + +{{- /* Generate FromJson/ToJson implementations for each message */ -}} +{{- range $idx, $msg := $.Messages }} +{{- if eq $msg.Name "Session" }} + +FNakamaSession FNakamaSession::FromJson(const TSharedPtr& Json) noexcept +{ + FNakamaSession Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ $jsonFieldName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + Result.{{ $fieldName }}.Add(Item->AsNumber()); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Result.{{ $fieldName }}.Add(Item->AsBool()); +{{- else if isMessageType $fieldType }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ $fieldName }}.Add(FNakama{{ $fieldType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- end }} + } + } + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetIntegerField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetBoolField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetNumberField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if containsString $fieldType "Timestamp" }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if isEnumType $fieldType }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast<{{ enumUEName $fieldType }}>(Json->GetIntegerField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), NestedObj)) + { + Result.{{ $fieldName }} = FNakama{{ $fieldType }}::FromJson(*NestedObj); + } + } +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + {{- end }} + Result.ParseTokens(); + return Result; +} + +TSharedPtr FNakamaSession::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + return Json; +} +{{- else }} + +FNakama{{ $msg.Name }} FNakama{{ $msg.Name }}::FromJson(const TSharedPtr& Json) noexcept +{ + FNakama{{ $msg.Name }} Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ $jsonFieldName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + Result.{{ $fieldName }}.Add(Item->AsNumber()); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Result.{{ $fieldName }}.Add(Item->AsBool()); +{{- else if isMessageType $fieldType }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ $fieldName }}.Add(FNakama{{ $fieldType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- end }} + } + } + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetIntegerField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetBoolField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetNumberField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if containsString $fieldType "Timestamp" }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if isEnumType $fieldType }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast<{{ enumUEName $fieldType }}>(Json->GetIntegerField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), NestedObj)) + { + Result.{{ $fieldName }} = FNakama{{ $fieldType }}::FromJson(*NestedObj); + } + } +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + {{- end }} + return Result; +} + +TSharedPtr FNakama{{ $msg.Name }}::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + return Json; +} +{{- end }} +{{- end }} + +// --- FNakamaClientConfig --- + +FString FNakamaClientConfig::GetBaseUrl() const noexcept +{ + const FString Scheme = bUseSSL ? TEXT("https") : TEXT("http"); + return FString::Printf(TEXT("%s://%s:%d"), *Scheme, *Host, Port); +} + diff --git a/cmd/codegen/templates/nakama-types.ue.h.tmpl b/cmd/codegen/templates/nakama-types.ue.h.tmpl new file mode 100644 index 000000000..73f2518e2 --- /dev/null +++ b/cmd/codegen/templates/nakama-types.ue.h.tmpl @@ -0,0 +1,175 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaTypes.generated.h" + +// Forward declarations +{{- range $idx, $msg := $.Messages }} +struct FNakama{{ $msg.Name }}; +{{- end }} + +{{- /* Generate enum definitions */ -}} +{{- range $idx, $enum := $.Enums }} + +/** {{ $enum.Comment }} */ +UENUM(BlueprintType) +enum class ENakama{{ $enum.Name }} : uint8 +{ + {{- range $fidx, $field := $enum.Fields }} + {{ $field.Name }} = {{ $field.Integer }}{{ if not (isLastEnumField $enum.Fields $fidx) }},{{ end }} + {{- end }} +}; +{{- end }} + +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaError +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + FString Message; + + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + int32 Code = 0; +}; + +{{- template "errorcodes" "Nakama" }} + +// Forward declarations +{{- range $idx, $msg := $.Messages }} +struct FNakama{{ $msg.Name }}; +{{- end }} + +{{- /* Generate struct definitions for each proto message */ -}} +{{- range $idx, $msg := $.Messages }} +{{- if eq $msg.Name "Session" }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaSession +{ + GENERATED_BODY() + + {{- range $fidx, $field := $msg.Fields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealFieldType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; + {{- end }} + {{- range $fidx, $field := $msg.MapFields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealMapType $field.Type }} {{ $field.Name | pascalCase }}; + {{- end }} + + /** User ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + FString UserId; + + /** Username parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + FString Username; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + int64 RefreshTokenExpiresAt = 0; + + /** Session variables from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Nakama") + TMap Vars; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static FNakamaSession FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; +{{- else }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakama{{ $msg.Name }} +{ + GENERATED_BODY() + + {{- range $fidx, $field := $msg.Fields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealFieldType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; + {{- end }} + {{- range $fidx, $field := $msg.MapFields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Nakama") + {{ getUnrealMapType $field.Type }} {{ $field.Name | pascalCase }}; + {{- end }} + + static FNakama{{ $msg.Name }} FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; +{{- end }} +{{- end }} + +enum class ENakamaRequestAuth : uint8 +{ + None, + Basic, + Bearer, + HttpKey +}; + +/** Low-level API client configuration. */ +USTRUCT(BlueprintType) +struct NAKAMAAPI_API FNakamaClientConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + FString ServerKey; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + FString Host; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + int32 Port = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Nakama") + bool bUseSSL = false; + + FString GetBaseUrl() const noexcept; +}; + diff --git a/cmd/codegen/templates/nakama.ue.cpp.tmpl b/cmd/codegen/templates/nakama.ue.cpp.tmpl new file mode 100644 index 000000000..e9188d0f4 --- /dev/null +++ b/cmd/codegen/templates/nakama.ue.cpp.tmpl @@ -0,0 +1,448 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "Nakama.h" +#include "Containers/Ticker.h" +#include "Modules/ModuleManager.h" + +bool Nakama::IsTransientError(const FNakamaError& Error) noexcept +{ + switch (Error.Code) + { + case 0: // Connection failed (TCP refused, DNS timeout, etc.) + case 4: // DEADLINE_EXCEEDED + case 13: // INTERNAL + case 14: // UNAVAILABLE + return true; + default: + return false; + } +} + +float Nakama::CalculateBackoff(int32 Attempt, const FNakamaRetryConfig& Config) noexcept +{ + const float ExponentialDelay = FMath::Pow(2.0f, static_cast(Attempt - 1)) * Config.BaseDelayMs; + const float Jitter = FMath::FRand() * ExponentialDelay; + return Jitter / 1000.0f; +} + +namespace +{ + +/** Optionally refresh the session before calling the RPC. */ +void MaybeRefreshThenCall( + const TSharedRef& SessionState, + const FNakamaClientConfig& ClientConfig, + const FNakamaRetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)(FNakamaError(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + NakamaApi::SessionRefresh( + ClientConfig, + SessionState->RefreshToken, + {}, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const FNakamaSession& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const FNakamaError& Error) + { + (*OnError)(FNakamaError(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace + +{{- /* Generate free function implementations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} +{{- $resultType := asyncResultType $returnType }} +TNakamaFuture<{{ $resultType }}Result> Nakama::{{ $rpc.Name }}( + const FNakamaClientConfig& ClientConfig, + {{- if $needsSession }} + const FNakamaSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- /* Pass 1: non-map fields (flattening message types) */ -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }}, + {{- end }} + {{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- /* Pass 2: map fields from flattened messages */ -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageMapFields $field.Type }} + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- end }} + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + {{- if $needsSession }} + auto SessionState = MakeShared(Session); + {{- end }} + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{{ "{{" }}}, Error, true}); + } + }; + + *DoRequest = [FutureState, {{- if $needsSession }}SessionState, {{- end }}DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- else }} + , {{ $field.Name | pascalCase }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- end }} + ]() + { +{{- if $needsSession }} + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- else }} + , {{ $field.Name | pascalCase }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- end }} + ]() + { + {{- /* Reconstruct structs from flattened params for the NakamaApi call */ -}} + {{- if $hasParams }} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{ getUnrealReturnType $field.Type }} {{ $field.Name | pascalCase }}; + {{- range $sf := getMessageFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- end }} + {{- end }} + {{- end }} + NakamaApi::{{ $rpc.Name }}( + ClientConfig, + *SessionState, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + [FutureState, DoRequest, OnError](const FNakama{{ $returnType.Name }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); +{{- else }} + {{- /* Reconstruct structs from flattened params for the NakamaApi call */ -}} + {{- if $hasParams }} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{ getUnrealReturnType $field.Type }} {{ $field.Name | pascalCase }}; + {{- range $sf := getMessageFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- end }} + {{- end }} + {{- end }} + NakamaApi::{{ $rpc.Name }}( + ClientConfig, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + [FutureState, DoRequest, OnError](const FNakama{{ $returnType.Name }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); +{{- end }} + }; + + (*DoRequest)(); + + return TNakamaFuture<{{ $resultType }}Result>(FutureState); +} +{{- if $needsSession }} + +TNakamaFuture<{{ $resultType }}Result> Nakama::{{ $rpc.Name }}( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if ne $field.Name "http_key" }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }}, + {{- end }} + {{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (ne $field.Name "http_key") (shouldFlattenMessage $field.Type $field.Repeated) }} + {{- range $sf := getMessageMapFields $field.Type }} + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- end }} + const FNakamaRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FNakamaError& Error) + { + if (Nakama::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Nakama::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{{ "{{" }}}, Error, true}); + } + }; + + *DoRequest = [FutureState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken, HttpKey + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if ne $field.Name "http_key" }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + , {{ $sf.Name | pascalCase }} + {{- end }} + {{- else }} + , {{ $field.Name | pascalCase }} + {{- end }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- end }} + ]() + { + {{- /* Reconstruct structs from flattened params for the NakamaApi call */ -}} + {{- if $hasParams }} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (ne $field.Name "http_key") (shouldFlattenMessage $field.Type $field.Repeated) }} + {{ getUnrealReturnType $field.Type }} {{ $field.Name | pascalCase }}; + {{- range $sf := getMessageFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- range $sf := getMessageMapFields $field.Type }} + {{ $field.Name | pascalCase }}.{{ $sf.Name | pascalCase }} = {{ $sf.Name | pascalCase }}; + {{- end }} + {{- end }} + {{- end }} + {{- end }} + NakamaApi::{{ $rpc.Name }}( + ClientConfig, + HttpKey, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if ne $field.Name "http_key" }} + {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + [FutureState, DoRequest, OnError](const FNakama{{ $returnType.Name }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FNakamaVoidResult{FNakamaVoid{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); + }; + + (*DoRequest)(); + + return TNakamaFuture<{{ $resultType }}Result>(FutureState); +} +{{- end }} +{{- end }} + +// Module implementation +class FNakamaModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogNakama, Log, TEXT("Nakama module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogNakama, Log, TEXT("Nakama module shutting down")); + } +}; + +IMPLEMENT_MODULE(FNakamaModule, Nakama) diff --git a/cmd/codegen/templates/nakama.ue.h.tmpl b/cmd/codegen/templates/nakama.ue.h.tmpl new file mode 100644 index 000000000..766ab8eda --- /dev/null +++ b/cmd/codegen/templates/nakama.ue.h.tmpl @@ -0,0 +1,170 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "NakamaApi.h" +#include "NakamaFuture.h" + +/** Tag type used as the value type for RPCs that return no data. */ +struct NAKAMA_API FNakamaVoid {}; + +struct NAKAMA_API FNakamaVoidResult +{ + using ValueType = FNakamaVoid; + FNakamaVoid Value{}; + FNakamaError Error; + bool bIsError = true; +}; +{{- range $idx, $typeName := uniqueReturnTypes $.Rpcs }} + +struct NAKAMA_API FNakama{{ $typeName }}Result +{ + using ValueType = FNakama{{ $typeName }}; + FNakama{{ $typeName }} Value{}; + FNakamaError Error; + bool bIsError = true; +}; +{{- end }} + +/** Retry configuration for transient error handling + session auto-refresh. */ +struct NAKAMA_API FNakamaRetryConfig +{ + /** Maximum number of retry attempts (0 = no retries). */ + int32 MaxRetries = 4; + + /** Base delay in milliseconds before first retry. Actual delay uses exponential backoff with full jitter. */ + int32 BaseDelayMs = 500; + + /** Whether to automatically refresh expired sessions before making API calls. */ + bool bAutoRefreshSession = true; + + /** How many seconds before expiry a session is considered "about to expire" and gets refreshed. */ + int64 AutoRefreshBufferSeconds = 300; + + /** HTTP request timeout in seconds. */ + float Timeout = 10.0f; + + /** + * Called after a session is automatically refreshed, so callers can persist the updated session. + * + * If the callback captures a UObject pointer, set OnSessionRefreshedOwner to that object. + * The callback will be skipped if the owner has been garbage-collected, preventing + * dangling-pointer crashes during in-flight retries. + */ + TFunction OnSessionRefreshed; + + /** Optional weak owner for OnSessionRefreshed. When set, the callback is skipped if the owner is stale. */ + TWeakObjectPtr OnSessionRefreshedOwner; +}; + +/** + * High-level Nakama API: free functions with retry logic + session auto-refresh. + * + * Transient errors (UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED) are + * automatically retried with exponential backoff and full jitter. + */ +namespace Nakama +{ + /** Returns true if the error code is considered transient (eligible for retry). */ + NAKAMA_API bool IsTransientError(const FNakamaError& Error) noexcept; + + /** Compute backoff delay in seconds for a given attempt using exponential backoff with full jitter. */ + NAKAMA_API float CalculateBackoff(int32 Attempt, const FNakamaRetryConfig& Config) noexcept; + +{{- /* Generate free function declarations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} +{{- $resultType := asyncResultType $returnType }} + + /** {{ $rpc.Comment }} */ + NAKAMA_API TNakamaFuture<{{ $resultType }}Result> {{ $rpc.Name }}( + const FNakamaClientConfig& ClientConfig, + {{- if $needsSession }} + const FNakamaSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- /* Pass 1: non-map fields (flattening message types) */ -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }}{{ if canDefaultFlattenedFields $requestType.Fields $fidx }}{{ getUnrealParamDefault $sf.Type $sf.Repeated }}{{ end }}, + {{- end }} + {{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- /* Pass 2: map fields from flattened messages (with defaults, right before RetryConfig) */ -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageMapFields $field.Type }} + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }} = {}, + {{- end }} + {{- end }} + {{- end -}} + {{- end }} + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- if $needsSession }} + + /** {{ $rpc.Comment }} (Server-to-server with HTTP key) */ + NAKAMA_API TNakamaFuture<{{ $resultType }}Result> {{ $rpc.Name }}( + const FNakamaClientConfig& ClientConfig, + const FString& HttpKey, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if ne $field.Name "http_key" }} + {{- if shouldFlattenMessage $field.Type $field.Repeated }} + {{- range $sf := getMessageFields $field.Type }} + {{ getUnrealType $sf.Type $sf.Repeated }} {{ $sf.Name | pascalCase }}{{ if canDefaultFlattenedFields $requestType.Fields $fidx }}{{ getUnrealParamDefault $sf.Type $sf.Repeated }}{{ end }}, + {{- end }} + {{- else if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (ne $field.Name "http_key") (shouldFlattenMessage $field.Type $field.Repeated) }} + {{- range $sf := getMessageMapFields $field.Type }} + const {{ getUnrealMapType $sf.Type }}& {{ $sf.Name | pascalCase }} = {}, + {{- end }} + {{- end }} + {{- end -}} + {{- end }} + const FNakamaRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +{{- end }} +} diff --git a/cmd/codegen/templates/satori-api.ue.cpp.tmpl b/cmd/codegen/templates/satori-api.ue.cpp.tmpl new file mode 100644 index 000000000..85d8c5cdb --- /dev/null +++ b/cmd/codegen/templates/satori-api.ue.cpp.tmpl @@ -0,0 +1,769 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "SatoriApi.h" +#include "SatoriHttpHelper.h" +#include "GenericPlatform/GenericPlatformHttp.h" + +#include "Modules/ModuleManager.h" + +DEFINE_LOG_CATEGORY(LogSatori); + +using namespace SatoriHttpInternal; + +// --- FSatoriSession JWT helpers --- + +bool FSatoriSession::ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept +{ + TArray Parts; + Jwt.ParseIntoArray(Parts, TEXT(".")); + if (Parts.Num() < 2) + { + return false; + } + + // Base64url -> standard Base64 + FString Payload = Parts[1]; + Payload.ReplaceInline(TEXT("-"), TEXT("+")); + Payload.ReplaceInline(TEXT("_"), TEXT("/")); + + // Pad to multiple of 4 + while (Payload.Len() % 4 != 0) + { + Payload += TEXT("="); + } + + TArray DecodedBytes; + if (!FBase64::Decode(Payload, DecodedBytes)) + { + return false; + } + + const FUTF8ToTCHAR Converter(reinterpret_cast(DecodedBytes.GetData()), DecodedBytes.Num()); + FString JsonString(Converter.Length(), Converter.Get()); + + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonString); + return FJsonSerializer::Deserialize(Reader, Out) && Out.IsValid(); +} + +void FSatoriSession::ParseTokens() noexcept +{ + IdentityId.Empty(); + TokenExpiresAt = 0; + TokenIssuedAt = 0; + RefreshTokenExpiresAt = 0; + + // Parse auth token + TSharedPtr TokenPayload; + if (!Token.IsEmpty() && ParseJwtPayload(Token, TokenPayload)) + { + if (TokenPayload->HasField(TEXT("iid"))) + { + IdentityId = TokenPayload->GetStringField(TEXT("iid")); + } + if (TokenPayload->HasField(TEXT("exp"))) + { + TokenExpiresAt = static_cast(TokenPayload->GetNumberField(TEXT("exp"))); + } + if (TokenPayload->HasField(TEXT("iat"))) + { + TokenIssuedAt = static_cast(TokenPayload->GetNumberField(TEXT("iat"))); + } + } + + // Parse refresh token + TSharedPtr RefreshPayload; + if (!RefreshToken.IsEmpty() && ParseJwtPayload(RefreshToken, RefreshPayload)) + { + if (RefreshPayload->HasField(TEXT("exp"))) + { + RefreshTokenExpiresAt = static_cast(RefreshPayload->GetNumberField(TEXT("exp"))); + } + } +} + +bool FSatoriSession::IsExpired(int64 BufferSeconds) const noexcept +{ + if (TokenExpiresAt == 0) + { + return true; + } + return (TokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +bool FSatoriSession::IsRefreshExpired(int64 BufferSeconds) const noexcept +{ + if (RefreshTokenExpiresAt == 0) + { + return true; + } + return (RefreshTokenExpiresAt - BufferSeconds) <= FDateTime::UtcNow().ToUnixTimestamp(); +} + +void FSatoriSession::Update(const FString& NewToken, const FString& NewRefreshToken) noexcept +{ + Token = NewToken; + RefreshToken = NewRefreshToken; + ParseTokens(); +} + +{{- /* Generate FromJson/ToJson implementations for each message */ -}} +{{- range $idx, $msg := $.Messages }} +{{- if eq $msg.Name "Session" }} + +FSatoriSession FSatoriSession::FromJson(const TSharedPtr& Json) noexcept +{ + FSatoriSession Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ $jsonFieldName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + Result.{{ $fieldName }}.Add(Item->AsNumber()); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Result.{{ $fieldName }}.Add(Item->AsBool()); +{{- else if isMessageType $fieldType }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ $fieldName }}.Add(FSatori{{ $fieldType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- end }} + } + } + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetIntegerField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetBoolField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetNumberField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if containsString $fieldType "Timestamp" }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if isEnumType $fieldType }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast<{{ enumUEName $fieldType }}>(Json->GetIntegerField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), NestedObj)) + { + Result.{{ $fieldName }} = FSatori{{ $fieldType }}::FromJson(*NestedObj); + } + } +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + {{- end }} + Result.ParseTokens(); + return Result; +} + +TSharedPtr FSatoriSession::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + return Json; +} +{{- else }} + +FSatori{{ $msg.Name }} FSatori{{ $msg.Name }}::FromJson(const TSharedPtr& Json) noexcept +{ + FSatori{{ $msg.Name }} Result; + if (!Json.IsValid()) + { + return Result; + } +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TArray>* ArrayPtr; + if (Json->TryGetArrayField(TEXT("{{ $jsonFieldName }}"), ArrayPtr)) + { + for (const auto& Item : *ArrayPtr) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + Result.{{ $fieldName }}.Add(static_cast(Item->AsNumber())); +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + Result.{{ $fieldName }}.Add(Item->AsNumber()); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Result.{{ $fieldName }}.Add(Item->AsBool()); +{{- else if isMessageType $fieldType }} + const TSharedPtr* ItemObj = nullptr; + if (Item->TryGetObject(ItemObj) && ItemObj) + { + Result.{{ $fieldName }}.Add(FSatori{{ $fieldType }}::FromJson(*ItemObj)); + } +{{- else }} + Result.{{ $fieldName }}.Add(Item->AsString()); +{{- end }} + } + } + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetIntegerField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint32") (containsString $fieldType "UInt32Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "uint64") (containsString $fieldType "UInt64Value") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast(Json->GetNumberField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetBoolField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if or (eq $fieldType "float") (eq $fieldType "double") }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetNumberField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if containsString $fieldType "Timestamp" }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = Json->GetStringField(TEXT("{{ $jsonFieldName }}")); + } +{{- else if isEnumType $fieldType }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + Result.{{ $fieldName }} = static_cast<{{ enumUEName $fieldType }}>(Json->GetIntegerField(TEXT("{{ $jsonFieldName }}"))); + } +{{- else }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* NestedObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), NestedObj)) + { + Result.{{ $fieldName }} = FSatori{{ $fieldType }}::FromJson(*NestedObj); + } + } +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if (Json->HasField(TEXT("{{ $jsonFieldName }}"))) + { + const TSharedPtr* MapObj; + if (Json->TryGetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj)) + { + for (const auto& Pair : (*MapObj)->Values) + { + Result.{{ $fieldName }}.Add(Pair.Key, Pair.Value->AsString()); + } + } + } + {{- end }} + return Result; +} + +TSharedPtr FSatori{{ $msg.Name }}::ToJson() const noexcept +{ + TSharedPtr Json = MakeShared(); +{{- range $fidx, $field := $msg.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} +{{- $fieldType := $field.Type }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") (containsString $fieldType "Timestamp") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Json->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") (containsString $fieldType "FloatValue") (containsString $fieldType "DoubleValue") }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Json->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if containsString $fieldType "Timestamp" }} + if (!{{ $fieldName }}.IsEmpty()) + { + Json->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if isEnumType $fieldType }} + Json->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else }} + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- end }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $msg.MapFields }} + {{- $fieldName := $field.Name | pascalCase }} + {{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Json->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } + {{- end }} + return Json; +} +{{- end }} +{{- end }} + +// --- FSatoriClientConfig --- + +FString FSatoriClientConfig::GetBaseUrl() const noexcept +{ + const FString Scheme = bUseSSL ? TEXT("https") : TEXT("http"); + return FString::Printf(TEXT("%s://%s:%d"), *Scheme, *Host, Port); +} + + + +{{- /* Generate free function implementations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isAuthLogout := eq $rpc.Name "AuthenticateLogout" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +void SatoriApi::{{ $rpc.Name }}( + const FSatoriClientConfig& Config, + {{- if $needsSession }} + const FSatoriSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout, + TSharedRef> CancellationToken) noexcept +{ + FString Endpoint = TEXT("{{ $rpc.Endpoint }}"); + {{- /* Handle path parameters like {group_id} */ -}} + {{- range $pidx, $param := $rpc.QueryParams }} + {{- if ne $param "" }} + { const FString Encoded_{{ $param | pascalCase }} = FGenericPlatformHttp::UrlEncode({{ $param | pascalCase }}); Endpoint = Endpoint.Replace(TEXT("{{ printf "{%s}" $param }}"), *Encoded_{{ $param | pascalCase }}); } + {{- end }} + {{- end }} + +{{- /* Handle query parameters (fields not in body) */ -}} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- $pathParams := $rpc.QueryParams }} + TArray QueryParams; +{{- range $fidx, $field := $requestType.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- $isPathParam := containsInSlice $pathParams $jsonFieldName }} +{{- $isQueryParam := and (not $isPathParam) (or $isGetRequest (eq $bodyField "") (and (ne $bodyField "*") (ne $jsonFieldName $bodyField))) }} +{{- if $isQueryParam }} +{{- if $field.Repeated }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + for (const FString& Item : {{ $fieldName }}) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode(Item)); + } +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + for (const int32& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), Item)); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + for (const int64& Item : {{ $fieldName }}) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), Item)); + } +{{- end }} +{{- else }} +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + QueryParams.Add(TEXT("{{ $jsonFieldName }}=") + FGenericPlatformHttp::UrlEncode({{ $fieldName }})); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%s"), {{ $fieldName }} ? TEXT("true") : TEXT("false"))); +{{- else if or (eq $fieldType "int32") (containsString $fieldType "Int32Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%d"), {{ $fieldName }})); + } +{{- else if or (eq $fieldType "int64") (containsString $fieldType "Int64Value") }} + if ({{ $fieldName }} != 0) + { + QueryParams.Add(FString::Printf(TEXT("{{ $jsonFieldName }}=%lld"), {{ $fieldName }})); + } +{{- end }} +{{- end }} +{{- end }} +{{- end }} + if (QueryParams.Num() > 0) + { + Endpoint += TEXT("?") + FString::Join(QueryParams, TEXT("&")); + } +{{- end }} + + ESatoriRequestAuth AuthType = {{ if and (or $isAuthenticate $isSessionRefresh) (not $isAuthLogout) }}ESatoriRequestAuth::Basic{{ else }}ESatoriRequestAuth::Bearer{{ end }}; + +{{- /* Build request body and dispatch */ -}} +{{- $scalarBodyField := "" }} +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if eq $field.Name $bodyField }} +{{- if not (isMessageType $field.Type) }} +{{- $scalarBodyField = $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- if ne $scalarBodyField "" }} + + FString BodyString; + if ({{ $scalarBodyField }}.IsValid()) + { + BodyString = SerializeJsonEscaped({{ $scalarBodyField }}); + } + + SendRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), BodyString, AuthType, {{ if $needsSession }}Session.Token{{ else if $isAuthLogout }}Token{{ else }}TEXT(""){{ end }}, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FSatori{{ $returnType.Name }} Result = FSatori{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- else }} +{{- /* Object body or no body */ -}} + TSharedPtr Body; +{{- if $hasParams }} +{{- $bodyField := $rpc.BodyField }} +{{- $isGetRequest := eq $rpc.Method "GET" }} +{{- if and (ne $bodyField "") (ne $bodyField "*") }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if eq $field.Name $bodyField }} +{{- $fieldName := $field.Name | pascalCase }} + Body = {{ $fieldName }}.ToJson(); +{{- end }} +{{- end }} +{{- else if and (eq $bodyField "*") (not $isGetRequest) }} + Body = MakeShared(); +{{- range $fidx, $field := $requestType.Fields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $fieldType := $field.Type }} +{{- $jsonFieldName := $field.Name }} +{{- if $field.Repeated }} + if ({{ $fieldName }}.Num() > 0) + { + TArray> Array; + for (const auto& Item : {{ $fieldName }}) + { +{{- if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") }} + Array.Add(MakeShared(Item)); +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Array.Add(MakeShared(Item)); +{{- else if isMessageType $fieldType }} + Array.Add(MakeShared(Item.ToJson())); +{{- else }} + Array.Add(MakeShared(Item)); +{{- end }} + } + Body->SetArrayField(TEXT("{{ $jsonFieldName }}"), Array); + } +{{- else if or (eq $fieldType "string") (containsString $fieldType "StringValue") }} + if (!{{ $fieldName }}.IsEmpty()) + { + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); + } +{{- else if or (eq $fieldType "bool") (containsString $fieldType "BoolValue") }} + Body->SetBoolField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if or (eq $fieldType "int32") (eq $fieldType "int64") (eq $fieldType "uint32") (eq $fieldType "uint64") (eq $fieldType "float") (eq $fieldType "double") (containsString $fieldType "Int32Value") (containsString $fieldType "Int64Value") (containsString $fieldType "UInt32Value") (containsString $fieldType "UInt64Value") }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- else if isEnumType $fieldType }} + Body->SetNumberField(TEXT("{{ $jsonFieldName }}"), static_cast({{ $fieldName }})); +{{- else if isMessageType $fieldType }} + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}.ToJson()); +{{- else }} + Body->SetStringField(TEXT("{{ $jsonFieldName }}"), {{ $fieldName }}); +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} +{{- $fieldName := $field.Name | pascalCase }} +{{- $jsonFieldName := $field.Name }} + if ({{ $fieldName }}.Num() > 0) + { + TSharedPtr MapObj = MakeShared(); + for (const auto& Pair : {{ $fieldName }}) + { + MapObj->SetStringField(Pair.Key, Pair.Value); + } + Body->SetObjectField(TEXT("{{ $jsonFieldName }}"), MapObj); + } +{{- end }} +{{- end }} +{{- end }} + + MakeRequest(Config, Endpoint, TEXT("{{ $rpc.Method }}"), Body, AuthType, {{ if $needsSession }}Session.Token{{ else if $isAuthLogout }}Token{{ else }}TEXT(""){{ end }}, + [OnSuccess](TSharedPtr Json) + { + {{- if $hasReturn }} + FSatori{{ $returnType.Name }} Result = FSatori{{ $returnType.Name }}::FromJson(Json); + if (OnSuccess) + { + OnSuccess(Result); + } + {{- else }} + if (OnSuccess) + { + OnSuccess(); + } + {{- end }} + }, + OnError, Timeout, CancellationToken); +{{- end }} +} +{{- end }} + +// Module implementation +class FSatoriApiModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogSatori, Log, TEXT("SatoriApi module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogSatori, Log, TEXT("SatoriApi module shutting down")); + } +}; + +IMPLEMENT_MODULE(FSatoriApiModule, SatoriApi) diff --git a/cmd/codegen/templates/satori-api.ue.h.tmpl b/cmd/codegen/templates/satori-api.ue.h.tmpl new file mode 100644 index 000000000..274cf011b --- /dev/null +++ b/cmd/codegen/templates/satori-api.ue.h.tmpl @@ -0,0 +1,212 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include +#include "CoreMinimal.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" + +#include "SatoriApi.generated.h" + +SATORIAPI_API DECLARE_LOG_CATEGORY_EXTERN(LogSatori, Log, All); + +// Forward declarations +{{- range $idx, $msg := $.Messages }} +struct FSatori{{ $msg.Name }}; +{{- end }} + +{{- /* Generate enum definitions */ -}} +{{- range $idx, $enum := $.Enums }} + +/** {{ $enum.Comment }} */ +UENUM(BlueprintType) +enum class ESatori{{ $enum.Name }} : uint8 +{ + {{- range $fidx, $field := $enum.Fields }} + {{ $field.Name }} = {{ $field.Integer }}{{ if not (isLastEnumField $enum.Fields $fidx) }},{{ end }} + {{- end }} +}; +{{- end }} + +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriError +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Satori") + FString Message; + + UPROPERTY(BlueprintReadWrite, Category = "Satori") + int32 Code = 0; + +}; + +{{- template "errorcodes" "Satori" }} + +{{- /* Generate struct definitions for each proto message */ -}} +{{- range $idx, $msg := $.Messages }} +{{- if eq $msg.Name "Session" }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriSession +{ + GENERATED_BODY() + + {{- range $fidx, $field := $msg.Fields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + {{ getUnrealFieldType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; + {{- end }} + {{- range $fidx, $field := $msg.MapFields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + {{ getUnrealMapType $field.Type }} {{ $field.Name | pascalCase }}; + {{- end }} + + /** Identity ID parsed from the auth token JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + FString IdentityId; + + /** Auth token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 TokenExpiresAt = 0; + + /** Auth token issued-at (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 TokenIssuedAt = 0; + + /** Refresh token expiry (Unix timestamp) parsed from JWT. */ + UPROPERTY(BlueprintReadOnly, Category = "Satori") + int64 RefreshTokenExpiresAt = 0; + + /** True if the auth token expires within BufferSeconds from now. */ + bool IsExpired(int64 BufferSeconds = 0) const noexcept; + + /** True if the refresh token has expired (no buffer). */ + bool IsRefreshExpired(int64 BufferSeconds = 0) const noexcept; + + /** Replace tokens and re-parse JWT claims. */ + void Update(const FString& NewToken, const FString& NewRefreshToken) noexcept; + + static FSatoriSession FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; + +private: + static bool ParseJwtPayload(const FString& Jwt, TSharedPtr& Out) noexcept; + void ParseTokens() noexcept; +}; +{{- else }} + +/** {{ $msg.Comment }} */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatori{{ $msg.Name }} +{ + GENERATED_BODY() + + {{- range $fidx, $field := $msg.Fields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + {{ getUnrealFieldType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; + {{- end }} + {{- range $fidx, $field := $msg.MapFields }} + /** {{ $field.Comment.Message }} */ + UPROPERTY(BlueprintReadWrite, Category = "Satori") + {{ getUnrealMapType $field.Type }} {{ $field.Name | pascalCase }}; + {{- end }} + + static FSatori{{ $msg.Name }} FromJson(const TSharedPtr& Json) noexcept; + TSharedPtr ToJson() const noexcept; +}; +{{- end }} +{{- end }} + +enum class ESatoriRequestAuth : uint8 +{ + None, + Basic, + Bearer +}; + +/** Low-level API client configuration. */ +USTRUCT(BlueprintType) +struct SATORIAPI_API FSatoriClientConfig +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + FString ServerKey; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + FString Host; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + int32 Port = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Satori") + bool bUseSSL = false; + + FString GetBaseUrl() const noexcept; +}; + +/** Low-level Satori API: data types + free functions for HTTP RPCs (callback-based). */ +namespace SatoriApi +{ + +{{- /* Generate free function declarations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + + /** {{ $rpc.Comment }} */ + SATORIAPI_API void {{ $rpc.Name }}( + const FSatoriClientConfig& Config, + {{- if $needsSession }} + const FSatoriSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + TFunction OnSuccess, + {{- else }} + TFunction OnSuccess, + {{- end }} + TFunction OnError, + float Timeout = 10.0f, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +} diff --git a/cmd/codegen/templates/satori-client-bplib.ue.cpp.tmpl b/cmd/codegen/templates/satori-client-bplib.ue.cpp.tmpl new file mode 100644 index 000000000..70a9fdafb --- /dev/null +++ b/cmd/codegen/templates/satori-client-bplib.ue.cpp.tmpl @@ -0,0 +1,132 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "SatoriClientBlueprintLibrary.h" + +// ============================================================================ +// Async Action Classes Implementation +// ============================================================================ +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +// {{ $rpc.Name }} +USatoriClient{{ $rpc.Name }}* USatoriClient{{ $rpc.Name }}::{{ $rpc.Name }}( + UObject* WorldContextObject, + FSatoriClientConfig Client +{{- if $needsSession }}, + const FSatoriSession& Session +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }}, +{{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + const TMap& {{ $field.Name | pascalCase }} +{{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }} +{{- end }} +{{- end }}) +{ + USatoriClient{{ $rpc.Name }}* Action = NewObject(GetTransientPackage()); + Action->Client = Client; +{{- if $needsSession }} + Action->Session = Session; +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase }}; +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + Action->Stored{{ $field.Name | pascalCase }} = {{ $field.Name | pascalCase }}; +{{- end }} +{{- end }} + Action->RegisterWithGameInstance(WorldContextObject); + return Action; +} + +void USatoriClient{{ $rpc.Name }}::Activate() +{ + static const TCHAR* TraceScope_{{ $rpc.Name }} = TEXT("SatoriBP_{{ $rpc.Name }}"); + TRACE_CPUPROFILER_EVENT_SCOPE_STR(TraceScope_{{ $rpc.Name }}); + + TWeakObjectPtr WeakThis(this); + + SatoriApi::{{ $rpc.Name }}( + Client, +{{- if $needsSession }} + Session, +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + [&]() -> TSharedPtr { + if (Stored{{ $field.Name | pascalCase }}.Num() == 0) { return nullptr; } + TSharedPtr Json = MakeShared(); + for (const auto& Pair : Stored{{ $field.Name | pascalCase }}) + { + Json->SetStringField(Pair.Key, Pair.Value); + } + return Json; + }(), +{{- else }} + Stored{{ $field.Name | pascalCase }}, +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + Stored{{ $field.Name | pascalCase }}, +{{- end }} +{{- end }} +{{- if $hasReturn }} + [WeakThis](const FSatori{{ $returnType.Name }}& Result) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(Result); + Self->SetReadyToDestroy(); + } + }, +{{- else }} + [WeakThis]() + { + if (auto* Self = WeakThis.Get()) + { + Self->OnSuccess.Broadcast(); + Self->SetReadyToDestroy(); + } + }, +{{- end }} + [WeakThis](const FSatoriError& Error) + { + if (auto* Self = WeakThis.Get()) + { + Self->OnError.Broadcast(Error); + Self->SetReadyToDestroy(); + } + } + ); +} +{{- end }} diff --git a/cmd/codegen/templates/satori-client-bplib.ue.h.tmpl b/cmd/codegen/templates/satori-client-bplib.ue.h.tmpl new file mode 100644 index 000000000..7230521a6 --- /dev/null +++ b/cmd/codegen/templates/satori-client-bplib.ue.h.tmpl @@ -0,0 +1,114 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "SatoriApi.h" + +#include "SatoriClientBlueprintLibrary.generated.h" + +// ============================================================================ +// Dynamic delegates for async actions +// ============================================================================ + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatoriError, const FSatoriError&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSatoriSuccess); + +{{- /* Generate success delegates for each unique return type */ -}} +{{- range $idx, $typeName := uniqueReturnTypes $.Rpcs }} +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSatori{{ $typeName }}, const FSatori{{ $typeName }}&, Result); +{{- end }} + +// ============================================================================ +// Async Action Classes (one per RPC) +// ============================================================================ +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} + +/** + * {{ $rpc.Comment }} + */ +UCLASS(Transient) +class SATORIBLUEPRINTS_API USatoriClient{{ $rpc.Name }} : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: +{{- if $hasReturn }} + UPROPERTY(BlueprintAssignable) + FOnSatori{{ $returnType.Name }} OnSuccess; +{{- else }} + UPROPERTY(BlueprintAssignable) + FOnSatoriSuccess OnSuccess; +{{- end }} + + UPROPERTY(BlueprintAssignable) + FOnSatoriError OnError; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "{{ $rpc.Name }}"), Category = "Satori|Client") + static USatoriClient{{ $rpc.Name }}* {{ $rpc.Name }}( + UObject* WorldContextObject, + FSatoriClientConfig Client +{{- if $needsSession }}, + const FSatoriSession& Session +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }}, +{{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + const TMap& {{ $field.Name | pascalCase }} +{{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }} +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }}, + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }} +{{- end }} +{{- end }}); + + virtual void Activate() override; + +private: + FSatoriClientConfig Client; + +{{- if $needsSession }} + FSatoriSession Session; +{{- end }} +{{- if $hasParams }} +{{- range $fidx, $field := $requestType.Fields }} +{{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TMap Stored{{ $field.Name | pascalCase }}; +{{- else }} + {{ getUnrealFieldType $field.Type $field.Repeated }} Stored{{ $field.Name | pascalCase }}{{ getUnrealFieldDefault $field.Type $field.Repeated }}; +{{- end }} +{{- end }} +{{- range $fidx, $field := $requestType.MapFields }} + {{ getUnrealMapType $field.Type }} Stored{{ $field.Name | pascalCase }}; +{{- end }} +{{- end }} +}; +{{- end }} diff --git a/cmd/codegen/templates/satori.ue.cpp.tmpl b/cmd/codegen/templates/satori.ue.cpp.tmpl new file mode 100644 index 000000000..99dda283a --- /dev/null +++ b/cmd/codegen/templates/satori.ue.cpp.tmpl @@ -0,0 +1,258 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#include "Satori.h" +#include "Containers/Ticker.h" +#include "Modules/ModuleManager.h" + +bool Satori::IsTransientError(const FSatoriError& Error) noexcept +{ + switch (Error.Code) + { + case 0: // Connection failed (TCP refused, DNS timeout, etc.) + case 4: // DEADLINE_EXCEEDED + case 13: // INTERNAL + case 14: // UNAVAILABLE + return true; + default: + return false; + } +} + +float Satori::CalculateBackoff(int32 Attempt, const FSatoriRetryConfig& Config) noexcept +{ + const float ExponentialDelay = FMath::Pow(2.0f, static_cast(Attempt - 1)) * Config.BaseDelayMs; + const float Jitter = FMath::FRand() * ExponentialDelay; + return Jitter / 1000.0f; +} + +namespace +{ + +/** Optionally refresh the session before calling the RPC. */ +void MaybeRefreshThenCall( + const TSharedRef& SessionState, + const FSatoriClientConfig& ClientConfig, + const FSatoriRetryConfig& RetryConfig, + const TSharedRef>& CancellationToken, + const TSharedRef>& OnError, + TFunction OnReady) noexcept +{ + if (!RetryConfig.bAutoRefreshSession + || SessionState->RefreshToken.IsEmpty() + || !SessionState->IsExpired(RetryConfig.AutoRefreshBufferSeconds)) + { + OnReady(); + return; + } + + if (SessionState->IsRefreshExpired()) + { + (*OnError)(FSatoriError(TEXT("Refresh token has expired"), 16)); + return; + } + + auto OnSessionRefreshed = RetryConfig.OnSessionRefreshed; + auto OnSessionRefreshedOwner = RetryConfig.OnSessionRefreshedOwner; + SatoriApi::AuthenticateRefresh( + ClientConfig, + SessionState->RefreshToken, + [SessionState, OnSessionRefreshed, OnSessionRefreshedOwner, OnReady = MoveTemp(OnReady)](const FSatoriSession& RefreshedSession) mutable + { + SessionState->Update(RefreshedSession.Token, RefreshedSession.RefreshToken); + if (OnSessionRefreshed && (OnSessionRefreshedOwner.IsExplicitlyNull() || OnSessionRefreshedOwner.IsValid())) + { + OnSessionRefreshed(*SessionState); + } + OnReady(); + }, + [OnError](const FSatoriError& Error) + { + (*OnError)(FSatoriError(FString::Printf(TEXT("Session refresh failed: %s"), *Error.Message), Error.Code)); + }, + RetryConfig.Timeout, + CancellationToken); +} + +} // anonymous namespace + +{{- /* Generate free function implementations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $hasReturn := ne $returnType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} +{{- $resultType := asyncResultType $returnType }} +TSatoriFuture<{{ $resultType }}Result> Satori::{{ $rpc.Name }}( + const FSatoriClientConfig& ClientConfig, + {{- if $needsSession }} + const FSatoriSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + const FSatoriRetryConfig& RetryConfig, + TSharedRef> CancellationToken) noexcept +{ + auto FutureState = MakeShared::FState>(); + {{- if $needsSession }} + auto SessionState = MakeShared(Session); + {{- end }} + auto RetryCount = MakeShared(0); + auto DoRequest = MakeShared>(); + auto OnError = MakeShared>(); + + *OnError = [FutureState, RetryCount, DoRequest, OnError, RetryConfig](const FSatoriError& Error) + { + if (Satori::IsTransientError(Error) && *RetryCount < RetryConfig.MaxRetries) + { + (*RetryCount)++; + float Delay = Satori::CalculateBackoff(*RetryCount, RetryConfig); + FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateLambda([DoRequest](float) -> bool { (*DoRequest)(); return false; }), + Delay); + } + else + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{{ "{{" }}}, Error, true}); + } + }; + + *DoRequest = [FutureState, {{- if $needsSession }}SessionState, {{- end }}DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- end }} + ]() + { +{{- if $needsSession }} + MaybeRefreshThenCall(SessionState, ClientConfig, RetryConfig, CancellationToken, OnError, + [FutureState, SessionState, DoRequest, OnError, ClientConfig, RetryConfig, CancellationToken + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + , {{ $field.Name | pascalCase }} + {{- end -}} + {{- end }} + ]() + { + SatoriApi::{{ $rpc.Name }}( + ClientConfig, + *SessionState, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + [FutureState, DoRequest, OnError](const FSatori{{ $returnType.Name }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); + }); +{{- else }} + SatoriApi::{{ $rpc.Name }}( + ClientConfig, + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + {{- if $hasReturn }} + [FutureState, DoRequest, OnError](const FSatori{{ $returnType.Name }}& Result) + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve({{ $resultType }}Result{Result, {}, false}); + }, + {{- else }} + [FutureState, DoRequest, OnError]() + { + *DoRequest = nullptr; + *OnError = nullptr; + FutureState->Resolve(FSatoriVoidResult{FSatoriVoid{}, {}, false}); + }, + {{- end }} + *OnError, + RetryConfig.Timeout, + CancellationToken); +{{- end }} + }; + + (*DoRequest)(); + + return TSatoriFuture<{{ $resultType }}Result>(FutureState); +} +{{- end }} + +// Module implementation +class FSatoriModule : public IModuleInterface +{ +public: + virtual void StartupModule() override + { + UE_LOG(LogSatori, Log, TEXT("Satori module starting")); + } + + virtual void ShutdownModule() override + { + UE_LOG(LogSatori, Log, TEXT("Satori module shutting down")); + } +}; + +IMPLEMENT_MODULE(FSatoriModule, Satori) diff --git a/cmd/codegen/templates/satori.ue.h.tmpl b/cmd/codegen/templates/satori.ue.h.tmpl new file mode 100644 index 000000000..bf8a9f584 --- /dev/null +++ b/cmd/codegen/templates/satori.ue.h.tmpl @@ -0,0 +1,135 @@ +/* + * Copyright {{currentYear}} The Nakama Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is auto-generated. DO NOT EDIT. */ + +#pragma once + +#include "CoreMinimal.h" +#include "AsyncFuture.h" +#include "SatoriApi.h" + +/** Tag type used as the value type for RPCs that return no data. */ +struct SATORI_API FSatoriVoid {}; + +struct SATORI_API FSatoriVoidResult +{ + using ValueType = FSatoriVoid; + FSatoriVoid Value{}; + FSatoriError Error; + bool bIsError = true; +}; +{{- range $idx, $typeName := uniqueReturnTypes $.Rpcs }} + +struct SATORI_API FSatori{{ $typeName }}Result +{ + using ValueType = FSatori{{ $typeName }}; + FSatori{{ $typeName }} Value{}; + FSatoriError Error; + bool bIsError = true; +}; +{{- end }} + +/** + * Satori-flavoured alias for TAsyncFuture. + * All three .Next() overloads are inherited from TAsyncFuture and dispatch + * user callbacks to the game thread, making it safe to touch UObject*, + * fire delegates, or update UI from any .Next() callback. + */ +template +using TSatoriFuture = TAsyncFuture; + +/** Type trait for TSatoriFuture (delegates to TIsTAsyncFuture). */ +template using TIsTSatoriFuture = TIsTAsyncFuture; + +/** Retry configuration for transient error handling + session auto-refresh. */ +struct SATORI_API FSatoriRetryConfig +{ + /** Maximum number of retry attempts (0 = no retries). */ + int32 MaxRetries = 4; + + /** Base delay in milliseconds before first retry. Actual delay uses exponential backoff with full jitter. */ + int32 BaseDelayMs = 500; + + /** Whether to automatically refresh expired sessions before making API calls. */ + bool bAutoRefreshSession = true; + + /** How many seconds before expiry a session is considered "about to expire" and gets refreshed. */ + int64 AutoRefreshBufferSeconds = 300; + + /** HTTP request timeout in seconds. */ + float Timeout = 10.0f; + + /** + * Called after a session is automatically refreshed, so callers can persist the updated session. + * + * If the callback captures a UObject pointer, set OnSessionRefreshedOwner to that object. + * The callback will be skipped if the owner has been garbage-collected, preventing + * dangling-pointer crashes during in-flight retries. + */ + TFunction OnSessionRefreshed; + + /** Optional weak owner for OnSessionRefreshed. When set, the callback is skipped if the owner is stale. */ + TWeakObjectPtr OnSessionRefreshedOwner; +}; + +/** + * High-level Satori API: free functions with retry logic + session auto-refresh. + * + * Transient errors (UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED) are + * automatically retried with exponential backoff and full jitter. + */ +namespace Satori +{ + /** Returns true if the error code is considered transient (eligible for retry). */ + SATORI_API bool IsTransientError(const FSatoriError& Error) noexcept; + + /** Compute backoff delay in seconds for a given attempt using exponential backoff with full jitter. */ + SATORI_API float CalculateBackoff(int32 Attempt, const FSatoriRetryConfig& Config) noexcept; + +{{- /* Generate free function declarations for each RPC */ -}} +{{- range $idx, $rpc := $.Rpcs }} + +{{- $requestType := $rpc.RequestType -}} +{{- $returnType := $rpc.ReturnType -}} +{{- $hasParams := ne $requestType nil }} +{{- $isAuthenticate := containsString $rpc.Name "Authenticate" }} +{{- $isSessionRefresh := eq $rpc.Name "SessionRefresh" }} +{{- $needsSession := and (not $isAuthenticate) (not $isSessionRefresh) }} +{{- $resultType := asyncResultType $returnType }} + + /** {{ $rpc.Comment }} */ + SATORI_API TSatoriFuture<{{ $resultType }}Result> {{ $rpc.Name }}( + const FSatoriClientConfig& ClientConfig, + {{- if $needsSession }} + const FSatoriSession& Session, + {{- end }} + {{- if $hasParams -}} + {{- range $fidx, $field := $requestType.Fields }} + {{- if and (eq $field.Name $rpc.BodyField) (not (isMessageType $field.Type)) }} + TSharedPtr {{ $field.Name | pascalCase }}, + {{- else }} + {{ getUnrealType $field.Type $field.Repeated }} {{ $field.Name | pascalCase }}, + {{- end }} + {{- end -}} + {{- range $fidx, $field := $requestType.MapFields }} + const {{ getUnrealMapType $field.Type }}& {{ $field.Name | pascalCase }}, + {{- end -}} + {{- end }} + const FSatoriRetryConfig& RetryConfig = {}, + TSharedRef> CancellationToken = MakeShared>(false)) noexcept; +{{- end }} +} diff --git a/cmd/protoverify/go.mod b/cmd/protoverify/go.mod new file mode 100644 index 000000000..c97aedfdc --- /dev/null +++ b/cmd/protoverify/go.mod @@ -0,0 +1,5 @@ +module heroic-labs.com/nakama/sdk/protoverify + +go 1.25.5 + +require github.com/emicklei/proto v1.14.2 diff --git a/cmd/protoverify/go.sum b/cmd/protoverify/go.sum new file mode 100644 index 000000000..712834f7b --- /dev/null +++ b/cmd/protoverify/go.sum @@ -0,0 +1,2 @@ +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= diff --git a/cmd/protoverify/main.go b/cmd/protoverify/main.go new file mode 100644 index 000000000..6148a1554 --- /dev/null +++ b/cmd/protoverify/main.go @@ -0,0 +1,196 @@ +// Copyright 2026 GameUp Online, Inc. +// All rights reserved. +// +// NOTICE: All information contained herein is, and remains the property of GameUp +// Online, Inc. and its suppliers, if any. The intellectual and technical concepts +// contained herein are proprietary to GameUp Online, Inc. and its suppliers and may +// be covered by U.S. and Foreign Patents, patents in process, and are protected by +// trade secret or copyright law. Dissemination of this information or reproduction of +// this material is strictly forbidden unless prior written permission is obtained +// from GameUp Online, Inc. + +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/emicklei/proto" +) + +// Protobuf scalar types that don't need forward-declaration. +var builtinTypes = map[string]bool{ + "double": true, "float": true, + "int32": true, "int64": true, + "uint32": true, "uint64": true, + "sint32": true, "sint64": true, + "fixed32": true, "fixed64": true, + "sfixed32": true, "sfixed64": true, + "bool": true, "string": true, "bytes": true, +} + +type orderError struct { + File string + Context string // e.g. "rpc Authenticate" or "message Session" + Type string // the missing type name +} + +func (e orderError) String() string { + return fmt.Sprintf("%s: %s references %s, but it is not defined above", e.File, e.Context, e.Type) +} + +// stripPackage turns "api.MyType" into "MyType". +func stripPackage(typeName string) string { + if i := strings.LastIndex(typeName, "."); i >= 0 { + return typeName[i+1:] + } + return typeName +} + +// isExternal returns true for well-known google.protobuf types +// that are imported and don't need local declaration. +func isExternal(typeName string) bool { + return strings.HasPrefix(typeName, "google.protobuf.") +} + +func verifyFile(filename string, seen map[string]bool) []orderError { + fileBytes, err := os.ReadFile(filename) + if err != nil { + log.Fatalf("Failed to read file %s: %s", filename, err.Error()) + } + + parser := proto.NewParser(bytes.NewReader(fileBytes)) + parsed, err := parser.Parse() + if err != nil { + log.Fatalf("Failed to parse proto file %s: %s", filename, err.Error()) + } + + var errors []orderError + + // checkType verifies a type reference is already seen. + checkType := func(context, typeName string) { + if isExternal(typeName) || builtinTypes[typeName] { + return + } + stripped := stripPackage(typeName) + if builtinTypes[stripped] { + return + } + if !seen[stripped] { + errors = append(errors, orderError{ + File: filename, + Context: context, + Type: stripped, + }) + } + } + + proto.Walk( + parsed, + proto.WithEnum(func(enum *proto.Enum) { + name := enum.Name + if enum.Parent != nil { + if parentMsg, ok := enum.Parent.(*proto.Message); ok { + name = parentMsg.Name + "_" + enum.Name + } + } + seen[name] = true + }), + proto.WithMessage(func(message *proto.Message) { + if message.Name == "google.protobuf.EnumValueOptions" { + return + } + + // Register this message as seen before checking its fields, + // so self-referential types work. + seen[message.Name] = true + + // Check field types within this message. + for _, elem := range message.Elements { + switch f := elem.(type) { + case *proto.NormalField: + checkType(fmt.Sprintf("message %s field %s", message.Name, f.Name), f.Type) + case *proto.MapField: + checkType(fmt.Sprintf("message %s map field %s key", message.Name, f.Name), f.KeyType) + checkType(fmt.Sprintf("message %s map field %s value", message.Name, f.Name), f.Type) + case *proto.Oneof: + for _, oe := range f.Elements { + if oof, ok := oe.(*proto.OneOfField); ok { + checkType(fmt.Sprintf("message %s oneof field %s", message.Name, oof.Name), oof.Type) + } + } + } + } + }), + proto.WithRPC(func(rpc *proto.RPC) { + checkType(fmt.Sprintf("rpc %s request", rpc.Name), rpc.RequestType) + checkType(fmt.Sprintf("rpc %s return", rpc.Name), rpc.ReturnsType) + }), + ) + + return errors +} + +type fileList []string + +func (f *fileList) String() string { + return fmt.Sprint(*f) +} +func (f *fileList) Set(value string) error { + *f = append(*f, value) + return nil +} + +func main() { + var protoFiles fileList + flag.Var(&protoFiles, "proto", "Proto file(s) to verify, in processing order. May be specified multiple times.") + flag.Parse() + + if len(protoFiles) == 0 { + log.Fatalf("No proto files given. Usage: protoverify --proto file1.proto [--proto file2.proto ...]") + } + + // Types seen so far across all files (simulates codegen's sequential loading). + seen := make(map[string]bool) + var allErrors []orderError + + messageCount := 0 + enumCount := 0 + rpcCount := 0 + + for _, f := range protoFiles { + before := len(seen) + errs := verifyFile(f, seen) + allErrors = append(allErrors, errs...) + + // Count what was added in this file. + fileBytes, _ := os.ReadFile(f) + parser := proto.NewParser(bytes.NewReader(fileBytes)) + parsed, _ := parser.Parse() + proto.Walk(parsed, + proto.WithEnum(func(_ *proto.Enum) { enumCount++ }), + proto.WithMessage(func(m *proto.Message) { + if m.Name != "google.protobuf.EnumValueOptions" { + messageCount++ + } + }), + proto.WithRPC(func(_ *proto.RPC) { rpcCount++ }), + ) + _ = before + } + + if len(allErrors) > 0 { + for _, e := range allErrors { + fmt.Println(e) + } + fmt.Printf("FAIL: %d ordering error(s) found\n", len(allErrors)) + os.Exit(1) + } + + fmt.Printf("OK: all types are defined before use (%d file(s), %d enum(s), %d message(s), %d rpc(s))\n", + len(protoFiles), enumCount, messageCount, rpcCount) +} diff --git a/cmd/testrunner/go.mod b/cmd/testrunner/go.mod new file mode 100644 index 000000000..2b34ca50b --- /dev/null +++ b/cmd/testrunner/go.mod @@ -0,0 +1,8 @@ +module heroic-labs.com/nakama/sdk/testrunner + +go 1.25.5 + +require ( + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect +) diff --git a/cmd/testrunner/go.sum b/cmd/testrunner/go.sum new file mode 100644 index 000000000..64c0afa3e --- /dev/null +++ b/cmd/testrunner/go.sum @@ -0,0 +1,4 @@ +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= diff --git a/cmd/testrunner/main.go b/cmd/testrunner/main.go new file mode 100644 index 000000000..4408b49dc --- /dev/null +++ b/cmd/testrunner/main.go @@ -0,0 +1,308 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "sync" + "time" + + "golang.org/x/term" +) + +var shards []*TestShard + +func addTestShard(name string, reportDir string) { + logFile := filepath.Join(reportDir, "stdout.log") + shards = append(shards, &TestShard{ + Index: len(shards), + Name: name, + ReportDir: reportDir, + + LogFile: logFile, + LogLines: make([]string, NumLogLines), + LogLineIdx: 0, + }) +} + +var defaultShardNames = []string{ + "IntegrationTests.Nakama", + "IntegrationTests.NakamaBlueprint", + "IntegrationTests.NakamaWebSocketSubsystem", + "IntegrationTests.NakamaRt", + "IntegrationTests.Satori", + "IntegrationTests.SatoriBlueprint", +} + +var editorFlags = []string{ + "-nullrhi", + "-stdout", + "-FullStdOutLogOutput", + "-forcelogflush", + "-nosplash", + "-buildmachine", + "-unattended", + "-nopause", + "-nosound", +} + +type Report struct { + Devices []json.RawMessage `json:"devices"` + ReportCreatedOn string `json:"reportCreatedOn"` + Succeeded int `json:"succeeded"` + SucceededWithWarnings int `json:"succeededWithWarnings"` + Failed int `json:"failed"` + NotRun int `json:"notRun"` + InProcess int `json:"inProcess"` + TotalDuration float64 `json:"totalDuration"` + ComparisonExported bool `json:"comparisonExported"` + ComparisonExportDirectory string `json:"comparisonExportDirectory"` + Tests []json.RawMessage `json:"tests"` +} + +type TestEntry struct { + TestDisplayName string `json:"testDisplayName"` + FullTestPath string `json:"fullTestPath"` + State string `json:"state"` + Entries []struct { + Event struct { + Type string `json:"type"` + Message string `json:"message"` + } `json:"event"` + } `json:"entries"` +} + +func runEditor(editor, project string, testShard *TestShard) { + args := []string{project} + args = append(args, editorFlags...) + args = append(args, + fmt.Sprintf("-abslog=%s", filepath.Join(testShard.ReportDir, "unreal.log")), + fmt.Sprintf("-ReportOutputPath=%s", testShard.ReportDir), + fmt.Sprintf("-ExecCmds=Automation RunTests %s; Quit", testShard.Name), + ) + + cmd := exec.Command(editor, args...) + if cmd.Err != nil { + testShard.addLine(fmt.Sprintf("Error creating cmd: %s", cmd.Err)) + testShard.Status = ShardFailed + return + } + + if f, err := os.Create(testShard.LogFile); err == nil { + cmd.Stdout = f + cmd.Stderr = f + defer f.Close() + } + + if err := cmd.Run(); err != nil { + testShard.addLine(fmt.Sprintf("Editor exited with error: %s", err)) + testShard.Status = ShardFailed + return + } + + testShard.Status = ShardPassed +} + +func mergeReports(dir string) { + pattern := filepath.Join(dir, "shard-*", "index.json") + matches, _ := filepath.Glob(pattern) + + if len(matches) == 0 { + return + } + + var merged Report + for i, path := range matches { + data, err := os.ReadFile(path) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err) + continue + } + + data = stripBOM(data) + + var report Report + if err := json.Unmarshal(data, &report); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing %s: %v\n", path, err) + continue + } + + if i == 0 { + merged.Devices = report.Devices + merged.ReportCreatedOn = report.ReportCreatedOn + } + + merged.Succeeded += report.Succeeded + merged.SucceededWithWarnings += report.SucceededWithWarnings + merged.Failed += report.Failed + merged.NotRun += report.NotRun + merged.InProcess += report.InProcess + merged.TotalDuration += report.TotalDuration + merged.Tests = append(merged.Tests, report.Tests...) + } + + out, err := json.MarshalIndent(merged, "", "\t") + if err != nil { + fmt.Fprintf(os.Stderr, "Error marshaling merged report: %v\n", err) + return + } + + outPath := filepath.Join(dir, "index.json") + if err := os.WriteFile(outPath, out, 0644); err != nil { + fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", outPath, err) + return + } + + fmt.Printf("Merged %d shards (%d tests) -> %s\n", len(matches), len(merged.Tests), outPath) +} + +func printSummary(reportDir string, shards []*TestShard) { + var failed []TestEntry + passed := 0 + var crashedShards []string + + // Collect results from each shard's report (if it exists) + for _, s := range shards { + reportPath := filepath.Join(s.ReportDir, "index.json") + data, err := os.ReadFile(reportPath) + if err != nil { + // No report — shard crashed before writing results + if s.Status == ShardFailed { + crashedShards = append(crashedShards, s.Name) + } + continue + } + + data = stripBOM(data) + + var report Report + if err := json.Unmarshal(data, &report); err != nil { + crashedShards = append(crashedShards, s.Name) + continue + } + + for _, raw := range report.Tests { + var t TestEntry + if err := json.Unmarshal(raw, &t); err != nil { + continue + } + if t.State == "Fail" { + failed = append(failed, t) + } else { + passed++ + } + } + } + + fmt.Println() + fmt.Println("=========================================") + + totalFailed := len(failed) + len(crashedShards) + fmt.Printf(" Passed: %d Failed: %d\n", passed, totalFailed) + fmt.Println("=========================================") + + for _, t := range failed { + fmt.Printf(" \033[31mFAIL:\033[0m %s\n", t.TestDisplayName) + for _, e := range t.Entries { + if e.Event.Type == "Error" { + fmt.Printf(" %s\n", e.Event.Message) + break + } + } + } + + for _, name := range crashedShards { + fmt.Printf(" \033[31mCRASH:\033[0m %s (shard exited before producing results)\n", name) + } + + fmt.Println() + + if totalFailed > 0 { + os.Exit(1) + } +} + +func stripBOM(data []byte) []byte { + if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF { + return data[3:] + } + return data +} + +func main() { + ueRoot := os.Getenv("UNREAL_ENGINE") + if ueRoot == "" { + fmt.Fprintln(os.Stderr, "UNREAL_ENGINE is not set") + os.Exit(1) + } + + var editor string + switch runtime.GOOS { + case "windows": + editor = filepath.Join(ueRoot, "Engine", "Binaries", "Win64", "UnrealEditor-Cmd.exe") + case "darwin": + editor = filepath.Join(ueRoot, "Engine", "Binaries", "Mac", "UnrealEditor.app", "Contents", "MacOS", "UnrealEditor") + default: + editor = filepath.Join(ueRoot, "Engine", "Binaries", "Linux", "UnrealEditor") + } + + cwd, _ := os.Getwd() + project := filepath.Join(cwd, "IntegrationTests", "IntegrationTests.uproject") + reportDir := filepath.Join(cwd, "Reports", fmt.Sprintf("IntegrationTests_%s", time.Now().Format("2006-01-02_15-04-05"))) + + os.MkdirAll(reportDir, 0755) + + shardNames := defaultShardNames + if len(os.Args) > 1 { + shardNames = os.Args[1:] + } + + for i, shardName := range shardNames { + shardDir := filepath.Join(reportDir, fmt.Sprintf("shard-%d", i)) + os.MkdirAll(shardDir, 0755) + addTestShard(shardName, shardDir) + } + + fmt.Printf("Launching %d test shards in parallel...\n", len(shards)) + + // Do fancy stuff with logs if we're in a capable terminal. + isRichOutput := term.IsTerminal(int(os.Stdout.Fd())) + var wg sync.WaitGroup + for _, shard := range shards { + logDone := make(chan struct{}) + + // + // Start a test goroutine. + wg.Add(1) + go func(shard *TestShard) { + defer wg.Done() + defer close(logDone) + runEditor(editor, project, shard) + }(shard) + + // + // Watch for log file changes to update shard output. + if isRichOutput { + go func(shard *TestShard, done <-chan struct{}) { + updateShardLog(shard, done) + }(shard, logDone) + } + } + + // + // Make channel for when the tests wait group completes + testsComplete := make(chan struct{}) + go func() { + wg.Wait() + close(testsComplete) + }() + + drawStatusLoop(isRichOutput, shards, testsComplete) + + fmt.Println("All shards complete.") + mergeReports(reportDir) + printSummary(reportDir, shards) +} diff --git a/cmd/testrunner/testShard.go b/cmd/testrunner/testShard.go new file mode 100644 index 000000000..8d1730846 --- /dev/null +++ b/cmd/testrunner/testShard.go @@ -0,0 +1,235 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "golang.org/x/term" +) + +func moveCursorUp(n int) { fmt.Printf("\033[%dA", n) } +func moveCursorDown(n int) { fmt.Printf("\033[%dB", n) } +func clearLine() { fmt.Print("\033[2K\r") } +func hideCursor() { fmt.Print("\033[?25l") } +func showCursor() { fmt.Print("\033[?25h") } +func getTerminalWidth() int { + // term.GetSize returns (width, height, error) + width, _, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil { + // This will happen if you pipe the output to a file + // or run it in an environment without a real TTY. + return 80 + } + return width - 10 +} + +var NumLogLines int = 5 + +type ShardStatus int + +const ( + ShardRunning ShardStatus = iota + ShardPassed + ShardFailed +) + +type TestShard struct { + lock sync.RWMutex + + Index int + Name string + ReportDir string + + LogFile string + LogLines []string + LogLineIdx int + + Status ShardStatus + DrawnStatus ShardStatus + HeaderDrawn bool +} + +func (s *TestShard) statusIndicator() string { + switch s.Status { + case ShardPassed: + return "\033[32m✓\033[0m" // green check + case ShardFailed: + return "\033[31m✗\033[0m" // red X + default: + return "\033[33m⋯\033[0m" // yellow ellipsis + } +} + +func (s *TestShard) draw() { + if !s.HeaderDrawn { + clearLine() + fmt.Printf(" %s \033[1mShard %d:\033[0m %s\n", s.statusIndicator(), s.Index+1, s.Name) + fmt.Printf(" log: \033]8;;file://%s\033\\\033[2m%s\033[0m\033]8;;\033\\\n", s.LogFile, truncateString(s.LogFile, getTerminalWidth())) + + s.HeaderDrawn = true + s.DrawnStatus = s.Status + } else if s.Status != s.DrawnStatus { + // Status changed — redraw only the header line, skip past the log link + clearLine() + fmt.Printf(" %s \033[1mShard %d:\033[0m %s\n", s.statusIndicator(), s.Index+1, s.Name) + moveCursorDown(1) + s.DrawnStatus = s.Status + } else { + // No change — skip past header + log link + moveCursorDown(2) + } + + s.lock.RLock() + defer s.lock.RUnlock() + + // Since we will ring the log buffer, + + // We start at logLineIdx + for i := s.LogLineIdx; i < NumLogLines; i++ { + clearLine() + fmt.Printf(" \033[2m\033[0m \033[48;5;236m\033[37m %s \033[0m\n", s.LogLines[i]) + } + // And then go to the buffer start + // for the rest of the messages + for i := 0; i < s.LogLineIdx; i++ { + clearLine() + fmt.Printf(" \033[2m\033[0m \033[48;5;236m\033[37m %s \033[0m\n", s.LogLines[i]) + } +} + +func drawStatusLoop(isRichOutput bool, shards []*TestShard, testsComplete <-chan struct{}) { + if !isRichOutput { + for _, s := range shards { + fmt.Printf(" %s Shard %d: %s\n", s.statusIndicator(), s.Index+1, s.Name) + } + <-testsComplete + for _, s := range shards { + fmt.Printf(" %s Shard %d: %s\n", s.statusIndicator(), s.Index+1, s.Name) + } + } else { + // + // Rate limit for UI output + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + hideCursor() + + ui: + for { + select { + case <-testsComplete: + // Break the outer loop when done + break ui + case <-ticker.C: + // Basically redraw UI every tick + for _, s := range shards { + s.draw() + } + moveCursorUp(len(shards) * (NumLogLines + 2)) + } + } + + // Final redraw to pick up any status changes that occurred + // between the last tick and testsComplete closing. + for _, s := range shards { + s.draw() + } + moveCursorUp(len(shards) * (NumLogLines + 2)) + + // + // Reset the cursor and final report + moveCursorDown(len(shards) * (NumLogLines + 2)) + showCursor() + } +} + +func (s *TestShard) addLine(line string) { + s.lock.Lock() + defer s.lock.Unlock() + s.LogLines[s.LogLineIdx] = line + s.LogLineIdx++ + if s.LogLineIdx >= NumLogLines { + s.LogLineIdx = 0 + } +} + +func truncateString(s string, maxChars int) string { + runes := []rune(s) + if len(runes) <= maxChars { + return s + } + + // We need 3 characters for the "..." + // Split the remaining allowed characters between the start and the end + keep := maxChars - 3 + keepStart := keep / 2 + keepEnd := keep - keepStart + + return string(runes[:keepStart]) + "..." + string(runes[len(runes)-keepEnd:]) +} + +func updateShardLog(s *TestShard, done <-chan struct{}) { + file, err := os.Create(s.LogFile) + if err != nil { + fmt.Printf("Failed to open log file %s: %v\n", s.LogFile, err) + return + } + defer file.Close() + + reader := bufio.NewReader(file) + var partialLine string + + for { + line, err := reader.ReadString('\n') + + // + // Handle partial lines + if len(line) > 0 { + partialLine += line + // Only process when we know we have a complete line + if strings.HasSuffix(partialLine, "\n") { + s.addLine(truncateString(strings.TrimRight(partialLine, "\r\n"), getTerminalWidth())) + partialLine = "" // reset buffer + } + } + + if err == io.EOF { + select { + case <-done: + return + case <-time.After(200 * time.Millisecond): + } + + // + // Check the state of the file path on disk + pathInfo, errStat := os.Stat(s.LogFile) + if errStat == nil { + fileInfo, _ := file.Stat() + + // Detect rotation/recreation + if !os.SameFile(pathInfo, fileInfo) { + file.Close() + file, err = os.Open(s.LogFile) // Reopen the new file + if err != nil { + fmt.Printf("Failed to reopen rotated file: %v\n", err) + return + } + reader.Reset(file) + continue + } + } + + // Clear the sticky EOF state from bufio.Reader + reader.Reset(file) + + } else if err != nil { + fmt.Printf("Error reading file: %v\n", err) + return + } + } +} diff --git a/review.txt b/review.txt new file mode 100644 index 000000000..901103058 --- /dev/null +++ b/review.txt @@ -0,0 +1,125 @@ +● Good — go vet is clean. Here's my review: + + --- + Code Review — cmd/codegen + + 1. Bug: lowerCase panics on empty string + + func_maps.go:47 — strings.ToLower(s[:1]) + s[1:] will panic with an index-out-of-range if s is empty. Template functions are especially susceptible to unexpected inputs. + + "lowerCase": func(s string) string { + return strings.ToLower(s[:1]) + s[1:] // panic if s == "" + }, + + Fix: guard with if len(s) == 0 { return s }. + + --- + 2. Bug: stripDot panics on strings without a dot + + func_maps.go:90 — s[strings.LastIndex(s, ".")+1:] works by coincidence when there's no dot (LastIndex returns -1, so +1 = 0, giving the full string). This is the same pattern used in VisitOneofField at proto_visitors.go:102. + While it technically works, the resolveType closure at main.go:131 uses the same trick — it's fragile and unclear to readers. + + ★ Insight ───────────────────────────────────── + This LastIndex + 1 trick is a known Go idiom — strings.LastIndex returns -1 on miss, so +1 yields 0, giving the full string. It's correct but reads like a bug. A strings.Cut or explicit check makes intent clearer. + ───────────────────────────────────────────────── + + --- + 3. Bug: appendString is a no-op + + func_maps.go:49-52 — This function appends to a local copy of the string and returns true. The caller's string is never modified. Go strings are immutable values; s1 += s2 only changes the local parameter. + + "appendString": func(s1 string, s2 string) bool { + s1 += s2 // modifies local copy only + return true // always returns true, never the string + }, + + This looks like it was an attempt at mutation from templates. The appendStringNew right below it is the working version. If appendString is still referenced in templates it's silently doing nothing; if not, it's dead code. + + --- + 4. Deprecated API: strings.Title + + func_maps.go:20 — strings.Title has been deprecated since Go 1.18. It has known issues with Unicode word boundaries (e.g., handling apostrophes). Use cases.Title(language.English).String() from golang.org/x/text or + textcase.PascalCase which you already import. + + --- + 5. Swallowed error on partial template glob + + main.go:237 — filepath.Glob returns an error that's discarded: + + partials, _ := filepath.Glob(partialPattern) + + filepath.Glob only errors on malformed patterns (not missing files), so in practice this is unlikely to bite. But if someone introduces a bad pattern string, the error vanishes silently. + + --- + 6. loadApi error return value is never used + + main.go:165-186 — loadApi returns (Api, error) but addFile failures are returned into it, and the caller at main.go:218-221 does handle it. However, inside loadApi, the loop at line 182 silently discards the error from addFile: + + Wait — actually, looking again: there's no if err check — but there's no error variable either. Let me re-read: + + for _, f := range protoFiles { + api.addFile(f) // error is discarded + } + return api, nil // always returns nil error + + addFile returns an error, but the loop never checks it. The function signature promises an error return but it's always nil. Any proto parse failure is silently swallowed. + + ★ Insight ───────────────────────────────────── + This is a classic Go mistake — having an error return for the "interface" but forgetting to wire it up internally. The addFile method properly returns fmt.Errorf(...) on failure, but loadApi throws that away. A failing proto + file would produce a partial Api with no indication anything went wrong. + ───────────────────────────────────────────────── + + --- + 7. Template execution error is logged, not fatal + + main.go:259-261 — Template execution failures use log.Printf while every other error path uses log.Fatalf. This means a partial/corrupt output is written to stdout and the process exits 0. Downstream consumers (piping output to + a file) get a broken generated file with no error signal. + + if err := tmpl.Execute(os.Stdout, api); err != nil { + log.Printf(...) // should be log.Fatalf + } + + --- + 8. Regex compiled inside a loop on every addFile call + + main.go:63 — regexp.MustCompile([(.*?)]) is called inside the enum visitor callback, which runs for every enum in every file. This should be a package-level var compiled once. + + --- + 9. canDefaultFlattenedFields doesn't account for map fields + + func_maps.go:499-507 — This function checks whether all subsequent NormalFields are flattenable, but it ignores MapFields entirely. If the message has map fields after the current index, they aren't checked. Depending on how + the template uses this, map fields trailing the normal fields might not get default values, or the logic is incomplete. + + --- + 10. tryGetHttpMethod is fragile with substring matching + + proto_visitors.go:125-140 — strings.Contains(str, "get") will match any option name containing "get" as a substring (e.g., a hypothetical "budget" or "target"). This works today because the option names are google.api.http + sub-fields like "get", "post", etc. — but it's one refactor away from a subtle bug. Equality checks or prefix matching would be safer. + + ★ Insight ───────────────────────────────────── + The google.api.http annotation fields are always exactly get, post, put, delete, patch, custom. Using == instead of Contains would be both more correct and more readable. The current code would misclassify a field named + "forget" as GET. + ───────────────────────────────────────────────── + + --- + 11. VisitOneofField mutates the proto library's struct in place + + proto_visitors.go:102 — oneof.Name = oneof.Name[...] mutates the proto.OneOfField struct directly. If the same parsed proto is walked twice or inspected later, the name is already stripped. This is a side-effect that couples + the visitor to single-use walks. + + --- + Summary by severity + + ┌─────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Severity │ Issues │ + ├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ Bug (will bite you) │ #3 (appendString no-op), #6 (swallowed addFile error), #7 (partial output on template error exits 0) │ + ├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ Bug (edge case) │ #1 (lowerCase empty-string panic), #10 (substring HTTP method matching) │ + ├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ Code smell │ #4 (deprecated strings.Title), #5 (swallowed glob error), #8 (regex in hot path), #9 (canDefaultFlattenedFields ignores maps), #11 (mutating proto structs) │ + ├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ Readability │ #2 (LastIndex+1 idiom) │ + └─────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + The highest-priority fixes are #6 and #7 — they can cause silent data corruption in generated output. #3 is dead-code-level but confusing to future readers. diff --git a/triplets/arm-neon-android-release-heroic.cmake b/triplets/arm-neon-android-release-heroic.cmake deleted file mode 100644 index 7bfe7fef7..000000000 --- a/triplets/arm-neon-android-release-heroic.cmake +++ /dev/null @@ -1,12 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Android) -set(VCPKG_BUILD_TYPE release) -set(ANDROID_ARM_NEON ON) \ No newline at end of file diff --git a/triplets/arm64-android-release-heroic.cmake b/triplets/arm64-android-release-heroic.cmake deleted file mode 100644 index 187681f9b..000000000 --- a/triplets/arm64-android-release-heroic.cmake +++ /dev/null @@ -1,13 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Android) - -set(VCPKG_BUILD_TYPE release) - diff --git a/triplets/arm64-ios-release-heroic.cmake b/triplets/arm64-ios-release-heroic.cmake deleted file mode 100644 index 948a07024..000000000 --- a/triplets/arm64-ios-release-heroic.cmake +++ /dev/null @@ -1,17 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME iOS) - -set(VCPKG_BUILD_TYPE release) -set(VCPKG_OSX_DEPLOYMENT_TARGET "11") -set(VCPKG_OSX_ARCHITECTURES "arm64") -set(VCPKG_BUILD_TYPE release) - -include(${CMAKE_CURRENT_LIST_DIR}/feature-visibility-hidden.cmake) diff --git a/triplets/arm64-osx-release-heroic.cmake b/triplets/arm64-osx-release-heroic.cmake deleted file mode 100644 index 0bc674c17..000000000 --- a/triplets/arm64-osx-release-heroic.cmake +++ /dev/null @@ -1,15 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Darwin) -set(VCPKG_OSX_ARCHITECTURES arm64) - -set(VCPKG_OSX_DEPLOYMENT_TARGET "11") -set(VCPKG_BUILD_TYPE release) -include(${CMAKE_CURRENT_LIST_DIR}/feature-visibility-hidden.cmake) \ No newline at end of file diff --git a/triplets/arm64-windows-heroic.cmake b/triplets/arm64-windows-heroic.cmake deleted file mode 100644 index 6be183dd9..000000000 --- a/triplets/arm64-windows-heroic.cmake +++ /dev/null @@ -1,16 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(CMAKE_GENERATOR Visual Studio 17 2022) - -string(APPEND VCPKG_C_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") -string(APPEND VCPKG_CXX_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") - -list(APPEND VCPKG_KEEP_ENV_VARS "PATH" "Path" "VCPKG_ROOT") -set(VCPKG_ENV_PASSTHROUGH "PATH;Path;VCPKG_ROOT;") diff --git a/triplets/feature-visibility-hidden.cmake b/triplets/feature-visibility-hidden.cmake deleted file mode 100644 index 86a90e689..000000000 --- a/triplets/feature-visibility-hidden.cmake +++ /dev/null @@ -1,9 +0,0 @@ -# vcpkg dependencies are built statically and then linked into our shared lib. -# There is no way to reliably hide symbols from static lib dependencies in the shared lib. -# So instead of hiding symbols when linking, we build dependencies such that there is no public -# symbols in them in the first place. -# See following issues for context: -# - https://gitlab.kitware.com/cmake/cmake/-/issues/22177#note_983766 -# - https://gitlab.kitware.com/cmake/cmake/-/issues/16977 -string(APPEND VCPKG_C_FLAGS " -fvisibility=hidden -fvisibility-inlines-hidden") -string(APPEND VCPKG_CXX_FLAGS " -fvisibility=hidden -fvisibility-inlines-hidden") diff --git a/triplets/x64-android-release-heroic.cmake b/triplets/x64-android-release-heroic.cmake deleted file mode 100644 index a2df0cf85..000000000 --- a/triplets/x64-android-release-heroic.cmake +++ /dev/null @@ -1,13 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Android) - -set(VCPKG_BUILD_TYPE release) - diff --git a/triplets/x64-iphonesimulator-release-heroic.cmake b/triplets/x64-iphonesimulator-release-heroic.cmake deleted file mode 100644 index 38b115c89..000000000 --- a/triplets/x64-iphonesimulator-release-heroic.cmake +++ /dev/null @@ -1,18 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME iOS) - -# HeroicLabs additions to standard triplets: -set(VCPKG_BUILD_TYPE release) -set(VCPKG_OSX_DEPLOYMENT_TARGET "11") -set(VCPKG_OSX_ARCHITECTURES "x86_64") -set(VCPKG_OSX_SYSROOT iphonesimulator) - -include(${CMAKE_CURRENT_LIST_DIR}/feature-visibility-hidden.cmake) diff --git a/triplets/x64-linux-release-heroic.cmake b/triplets/x64-linux-release-heroic.cmake deleted file mode 100644 index 3327d1948..000000000 --- a/triplets/x64-linux-release-heroic.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# see https://forums.unrealengine.com/t/compiling-libraries-in-linux/400275 -# see https://forums.unrealengine.com/t/ue4-linux-use-libcxx-0-fails-to-compile/478297 -# see https://forums.unrealengine.com/t/using-bundled-libc-in-thirdparty/427863 - -set(VCPKG_ENV_PASSTHROUGH "UNREAL_ENGINE") -set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/../cmake/Toolchain-Unreal-Linux.cmake") - -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Linux) -set(VCPKG_BUILD_TYPE release) diff --git a/triplets/x64-osx-release-heroic.cmake b/triplets/x64-osx-release-heroic.cmake deleted file mode 100644 index 1ad6001c8..000000000 --- a/triplets/x64-osx-release-heroic.cmake +++ /dev/null @@ -1,17 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) - -set(VCPKG_LIBRARY_LINKAGE static) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(VCPKG_CMAKE_SYSTEM_NAME Darwin) -set(VCPKG_OSX_ARCHITECTURES x86_64) - -# HeroicLabs additions to standard triplets: -set(VCPKG_OSX_DEPLOYMENT_TARGET "10.15") -include(${CMAKE_CURRENT_LIST_DIR}/feature-visibility-hidden.cmake) \ No newline at end of file diff --git a/triplets/x64-windows-heroic.cmake b/triplets/x64-windows-heroic.cmake deleted file mode 100644 index f0e15d1f2..000000000 --- a/triplets/x64-windows-heroic.cmake +++ /dev/null @@ -1,16 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(CMAKE_GENERATOR Visual Studio 17 2022) - -string(APPEND VCPKG_C_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") -string(APPEND VCPKG_CXX_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") - -list(APPEND VCPKG_KEEP_ENV_VARS "PATH" "Path" "VCPKG_ROOT") -set(VCPKG_ENV_PASSTHROUGH "PATH;Path;VCPKG_ROOT;") diff --git a/triplets/x86-windows-heroic.cmake b/triplets/x86-windows-heroic.cmake deleted file mode 100644 index 3bbcb6d2d..000000000 --- a/triplets/x86-windows-heroic.cmake +++ /dev/null @@ -1,16 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x86) -set(VCPKG_CRT_LINKAGE dynamic) - -if (${PORT} MATCHES "nakama-") - set(VCPKG_LIBRARY_LINKAGE dynamic) -else() - set(VCPKG_LIBRARY_LINKAGE static) -endif() - -set(CMAKE_GENERATOR Visual Studio 17 2022) - -string(APPEND VCPKG_C_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") -string(APPEND VCPKG_CXX_FLAGS_RELEASE " /O1 /Ob2 /Gw /Gy") - -list(APPEND VCPKG_KEEP_ENV_VARS "PATH" "Path" "VCPKG_ROOT") -set(VCPKG_ENV_PASSTHROUGH "PATH;Path;VCPKG_ROOT;") diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json deleted file mode 100644 index 7a544f208..000000000 --- a/vcpkg-configuration.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "registries": - [ - { - "kind": "builtin", - "baseline": "62d01b70df227850b728f5050418b917ad6d2b32", - "packages": [] - }, - { - "kind": "git", - "repository": "https://github.com/heroiclabs/nakama-vcpkg-registry", - "baseline": "9056667f695d169827ab2e04d6d6a825d6f8bd8e", - "reference": "master", - "packages": ["nakama-sdk", "satori-sdk", "nakama-test"] - } - ] -} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index a40c932d6..000000000 --- a/vcpkg.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "nakama-unreal", - "version": "2.9.0", - "dependencies": [ - { - "name": "nakama-sdk", - "version>=": "2.8.0#13", - "features": [ - "apple-dylib" - ] - }, - { - "name": "satori-sdk", - "version>=": "0.0.0#1", - "features": [ - "apple-dylib" - ] - }, - { - "name": "nakama-test", - "features": [], - "version>=": "0.0.0#12" - } - ], - "default-features": [], - "builtin-baseline": "62d01b70df227850b728f5050418b917ad6d2b32" -}