question

longtoothgames avatar image
longtoothgames asked

How to properly call / use ListMultiplayerServers using Azure Functions and Unity

So I'm calling an Azure function from my client. The azure function calls ListMultiplayerServersAsync().

I was getting an error function side "Must call Client Login or GetEntityToken before calling this method" even though my client was logged in. Digging through the forums, I tried this

var entityResponse = await PlayFabAuthenticationAPI.GetEntityTokenAsync(new GetEntityTokenRequest());

which seemed to get rid of that error. I would like to know if this is right and if so, why. I'm really not sure what's going on here, especially since I did not use the return value for anything.

I then got the error "You must set your titleId before making an api call" function side. The titleID is set in the unity editor via playfab shared settings.

Even so, I also tried

            PlayFabSettings.staticSettings.TitleId = "C8E30";
            PlayFabSettings.TitleId = "C8E30";

Just before the function call, but it didn't seem to help. I then put the assignments in the static constructor, and once again got the "Must call Client Login or GetEntityToken before calling this method" error.

The call to the azure function is working, and I get a return when using just test data, but once I try to call ListMultiplayerServersAsync() I start getting the problems. Below is the Azure function code, minus most of the debugging stuff.

        [FunctionName("RequestServer")]
        public static async Task<Dictionary<string, object>> RequestServer([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
        {
            FunctionExecutionContext<Dictionary<string, string>> context = JsonConvert.DeserializeObject<FunctionExecutionContext<Dictionary<string, string>>>(await req.ReadAsStringAsync());
            Dictionary<string, string> args = context.FunctionArgument;

            Dictionary<string, object> functionResult = new Dictionary<string, object>();

//only put this line in because of the GetEntityToken error, not sure if it is proper usage
            var entityResponse = await PlayFabAuthenticationAPI.GetEntityTokenAsync(new GetEntityTokenRequest());


            ListMultiplayerServersRequest request = new ListMultiplayerServersRequest
            {
                BuildId = args["buildId"],
                Region = args["region"]
            };

//starting getting errors when I add this in, testing without it works fine
            ListMultiplayerServersResponse result = (await PlayFabMultiplayerAPI.ListMultiplayerServersAsync(request)).Result;




            //foreach(MultiplayerServerSummary server in result.MultiplayerServerSummaries)
            //{
            //    string serverInfo = $"ServerID: {server.ServerId} SessionID: {server.SessionId} NumOfPlayers: {server.ConnectedPlayers.Count} Status: {server.State}";
            //    functionResult.Add(server.ServerId, serverInfo);
            //}


            return functionResult;
        }//end of function

Below is the call from the client, which seems to be fine. I call it right from the playfab OnLoginSuccess() callback.

private void RequestServer()
{
            PlayFabCloudScriptAPI.ExecuteFunction(
                new ExecuteFunctionRequest()
                {
                    Entity = new PlayFab.CloudScriptModels.EntityKey()
                    {
                        Id = PlayFabSettings.staticPlayer.EntityId,
                        Type = PlayFabSettings.staticPlayer.EntityType,
                    },
                    FunctionName = AzureFunctionNames.RequestServer,
                    FunctionParameter = new Dictionary<string, string>() { { "buildId", gameConfiguration.BuildId }, {"region", Enum.GetName(typeof(AzureRegion), AzureRegion.EastUs) } },
                    GeneratePlayStreamEvent = false
                },
                OnRequestServerResult,
                OnPlayFabCallbackError);
}
 

I've been at this for a couple days now, I could really use some insight. The Azure function tutorials are pretty light compared to the legacy cloudscript, but they did help some. I'm sure it's something simple, but I'm still pretty new using PlayFab and Azure functions.

Any help would be much appreciated.

Edit: So here is the code that finally worked, I do have some questions still.

1. Mainly, why do we need to call

await PlayFabAuthenticationAPI.GetEntityTokenAsync(new GetEntityTokenRequest());

What is it doing behind the scenes because I'm not even using the return value, just calling the function with no parameters.

2. Is there any reason I shouldn't set the titleId and SecretKey in the class's static constructor so all the azure functions can access them with out assigning them every time?

Here is the actual code

[FunctionName("RequestServer")]
public static async Task<Dictionary<string, object>> RequestServer([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
	{
            FunctionExecutionContext<Dictionary<string, string>> context = JsonConvert.DeserializeObject<FunctionExecutionContext<Dictionary<string, string>>>(await req.ReadAsStringAsync());
            
            PlayFabSettings.staticSettings.TitleId = context.TitleAuthenticationContext.Id;
            PlayFabSettings.staticSettings.DeveloperSecretKey = Environment.GetEnvironmentVariable(secretKey, EnvironmentVariableTarget.Process);
            await PlayFabAuthenticationAPI.GetEntityTokenAsync(new GetEntityTokenRequest());




         Dictionary<string, string> args = context.FunctionArgument;
         Dictionary<string, object> functionResult = new Dictionary<string, object>();



         ListMultiplayerServersRequest request = new ListMultiplayerServersRequest
          {
              BuildId = args["buildId"],
              Region = args["region"]
          };

          var result = await PlayFabMultiplayerAPI.ListMultiplayerServersAsync(request);


          return functionResult;
}//end of function
sdksTitle DataAuthenticationmultiplayer
1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

longtoothgames avatar image longtoothgames commented ·

Found a solution, but still have some questions

0 Likes 0 ·

1 Answer

·
JayZuo avatar image
JayZuo answered

1. To call ListMultiplayerServers, you will need a vaild EntityToken as in Request Header. If you didn't set the EntityToken, you will get the error "Must call Client Login or GetEntityToken before calling this method". However, there is no need to call GetEntityTokenAsync method here as in FunctionExecutionContext, it already contains the EntityToken. We can utilize it as the following:

[FunctionName("RequestServer")]
public static async Task<PlayFabResult<PlayFab.MultiplayerModels.ListMultiplayerServersResponse>> RequestServer(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    FunctionExecutionContext<Dictionary<string, string>> context = JsonConvert.DeserializeObject<FunctionExecutionContext<Dictionary<string, string>>>(await req.ReadAsStringAsync());
    Dictionary<string, string> args = context.FunctionArgument;

    PlayFabSettings.staticSettings.TitleId = context.TitleAuthenticationContext.Id;

    var titleContext = new PlayFabAuthenticationContext
    {
        EntityId = context.TitleAuthenticationContext.Id,
        EntityToken = context.TitleAuthenticationContext.EntityToken
    };

    var request = new PlayFab.MultiplayerModels.ListMultiplayerServersRequest
    {
        BuildId = args["buildId"],
        Region = args["region"],
        AuthenticationContext = titleContext
    };

    return await PlayFabMultiplayerAPI.ListMultiplayerServersAsync(request);
}

2. It should be OK to set the TitleId and SecretKey in the class's static constructor. You can have a try.

2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

longtoothgames avatar image longtoothgames commented ·

Thank you, good info.

I see you are using the context to create an instance of PlayFabMultiplayerInstanceAPI and then calling ListMultiplayerServersAsync. I'm currently using the call

var result = await PlayFabMultiplayerAPI.ListMultiplayerServersAsync(request);

How would you do the above from your example using this static call instead of an instance?

I would guess I need to change the PlayFabSettings.staticPlayer fields, but I can't find a way to access it. Also, I'm assuming there is more overhead using the instanced method as opposed to the static method, am I wrong here?

Addtionally, I see you are not passing the apiSettings to the instance constructor, so I'm assuming the instance uses the PlayFabSettings.staticSettings by default?

Thanks for all your help

0 Likes 0 ·
JayZuo avatar image JayZuo ♦ longtoothgames commented ·

How would you do the above from your example using this static call instead of an instance?

We can also use the static call, it's just a little tricky. I've updated my code. You can have a look.

I see you are not passing the apiSettings to the instance constructor, so I'm assuming the instance uses the PlayFabSettings.staticSettings by default?

Yes, if we didn't pass apiSettings to the instance constructor, it will use the PlayFabSettings.staticSettings by default.

1 Like 1 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.