Jovo Community Plugin - PlayFab

Overview

This plugin for the Jovo Framework allows you to use the featurs of the PlayFab LiveOps game backend including anonymous login, player profiles, game statistics, user data and leaderboards.

This PlayFab plugin uses the classic APIs and not the newer Entity Programming Model.

Links:

Supports

  • Jovo Framework 4.x
  • Platforms: any (alexa, googleAssistant, core, web, etc.)

RIDR Lifecycle

This plugin is registered as part of the dialogue.start middleware and is meant to be used in component handlers and hooks after that point.

Install

Install the plugin into your Jovo project:

npm install @jovo-community/plugin-playfab --save

Register the plugin in:

app.js:

const { PlayFabPlugin } = require('@jovo-community/plugin-playfab');

const app = new App({
  plugins: [
    new PlayFabPlugin({/*...*/}),
  ],
});

app.ts:

import { PlayFabPlugin } from '@jovo-community/plugin-playfab';

const app = new App({
  plugins: [
    new PlayFabPlugin({/*...*/}),   
  ],
});

Configuration

The plugin has the following values:

new PlayFabPlugin({
  titleId: '',
  developerSecretKey: '',
  login: {
    autoLogin: true,
    extendedProfileKey: '',
    maxNewProfileRetries: 2,
    onNewProfile: (jovo: Jovo) => { return {/*...*/}},
    infoRequestParameters: {
      GetCharacterInventories: false,
      GetCharacterList: false,
      GetPlayerProfile: false,
      GetPlayerStatistics: false,
      GetTitleData: false,
      GetUserAccountInfo: false,
      GetUserData: false,
      GetUserInventory: false,
      GetUserReadOnlyData: false,
      GetUserVirtualCurrency: false,
    },
  },
  leaderboard: {
    topMax: 5,
    neighborMax: 2,
    userDataKeys: [],
    profileConstraints: {
      ShowAvatarUrl: false,
      ShowBannedUntil: false,
      ShowCampaignAttributions: false,
      ShowContactEmailAddresses: false,
      ShowCreated: false,
      ShowDisplayName: false,
      ShowExperimentVariants: false,
      ShowLastLogin: false,
      ShowLinkedAccounts: false,
      ShowLocations: false,
      ShowMemberships: false,
      ShowOrigination: false,
      ShowPushNotificationRegistrations: false,
      ShowStatistics: false,
      ShowTags: false,
      ShowTotalValueToDateInUsd: false,
      ShowValuesToDate: false,
    },
  },
}),

Here is a typical configuration:

{
  titleId: 'A999',
  developerSecretKey: 'XYZ...',
  login : {
    extendedProfileKey: 'extendedProfile',
    maxNewProfileRetries: 2,
    infoRequestParameters: {
      GetPlayerProfile: true,
      ProfileConstraints: {
        ShowDisplayName: true,
        ShowAvatarUrl: true,
      },
      GetPlayerStatistics: true,
      PlayerStatisticNames: ['score'],
      GetUserData: true,
      UserDataKeys: ['extendedProfile'],
    }, 
    onNewProfile: (jovo: Jovo) => { return { displayName: 'player1' }},  
  },
  leaderboard: {
    topMax: 5,
    neighborMax: 2,
    userDataKeys: ['extendedProfile'],
    profileConstraints: {
      ShowDisplayName: true,
      ShowAvatarUrl: true,
    }
  }
}
  • titleId: Required. The PlayFab game title id.
  • developerSecretKey: Required. Developer secret key needed to make PlayFab Server and Admin API calls. For more information, see Secret key management in the PlayFab documentation.
  • login: Configuration values for login. See login for more information.
  • leaderboard: Configuration values for the leaderboard. See leaderboard for more information.

login

Settings used when this.$playfab.login() is called. Login is called once per session.

The login configuration includes:

  • autoLogin: Automatically calls $playfab.login() each new session. Default value is true.
  • infoRequestParameters: The PlayFab player combined info request parameters. For more information, see GetPlayerCombinedInfoRequestParams in the PlayFab documentation.
  • onNewProfile: A callback that is called after a new player is created in PlayFab to provide a profile. For more information, see $playfab.login.
  • extendedProfileKey: If provided, the key in player user data to use for extended profile information. Default value is '' meaning there is no extended profile info. For more information, see $playfab.updateProfile.
  • maxNewProfileRetries: The number of times to call PlayFab with a new player's profile values (before continuing) when the call fails. A number between 0-10. Default value is 2. Use with caution. For more information, see $playfab.updateProfile.

leaderboard

Settings used when this.$playfab.getLeaderboard() is called. For more information, see $playfab.getLeaderboard.

The leaderboard configuration includes:

  • topMax: The number of players to return from the top of the leaderboard. Number between 0-100. Default value is 5.
  • neighborMax: The number of players (including the current player) to return immediately surrounding the current player. Number between 0-100. Default value is 2.
  • profileConstraints: The profile values to return for each player in the leaderboard. For more information, see PlayerProfileViewConstraints in the PlayFab documentation.
  • userDataKeys: An array of strings with each public player user data key that you want to return for each player in the leaderboard. This can be used to return extended profile information to use on the leaderboard.

Voice-only applications should keep this values for topMax and neighborMax to 5 or less. This value can be heigher when displaying the leaderboard. The total number of players in the returned leaderboard is the sum of topMax and neighborMax after duplicate entries are removed.

Caution: When userDataKeys is set, an API call is made for each player in the leaderboard to get the user data. Don't use when there are lots of players in the returned leaderboard.

Usage

Access functions and properties of this plugin using this.$playfab.

Functions

The following functions are available:

Behind the scenes, these functions call the PlayFab Server or Admin REST APIs.

The profile is in the format:

$playfab.login

Once (on the first request of a new session), $playfab.login() is automatically called when autoLogin is true (default). Otherwise $playfab.login() must be called manually. The is no return value. Check this.$session.data.playfab.loginStatus for status.

await this.$playfab.login();

First, PlayFabServer.LoginWithServerCustomId is called using Jovo $user.id as the ServerCustomId. If this is the first login for the ServerCustomId then a new player is created in PlayFab. Otherwise, the existing player is logged in. On success, the Jovo playfab.loginInfo session values is set.

New User

For a new user, a new player profile (with display name) is created only when the login.onNewProfile callback is set. Return a ProfileInfo object for the callback:

interface ProfileInfo {
  displayName?: string;
  avatarUrl?: string;
  extendedProfile?: AnyObject;
}

Here is an example of onNewProfile using the Player Generator Plugin:

async function onNewProfile(jovo: Jovo) {
  console.log('PlayFabPlugin:onNewProfile');

  const profile = jovo.$playergen.generateProfile();

  const playFabProfile = {
    displayName: profile.displayName,
    avatarUrl: profile.avatarUrl,
    extendedProfile: {
      color: profile.color,
      locale: jovo.$request.getLocale(),
    },
  } as ProfileInfo;

  return playFabProfile;
}

Once the profile is generated, $playfab.updateProfile() is called. Depending on your settings in PlayFab and the uniqueness of the displayName, the call to updateProfile() might fail. The plugin will try a total of login.maxNewProfileRetries times each time calling onNewProfile to get a different displayName (and other profile values). If none of those display name are accepted by PlayFab, the login process will continue and the user will not have a profile. You can call updateProfile() manually after that if needed.

The Jovo session value playfab.isDisplayNameUpdated will be set to true if the display name was set in PlayFab and false if it wasn't.

Existing User

If the login was for an existing user, the profile data is set on the Jovo session data key playfab.profile. It is important to set the login.infoRequestParameters values for GetPlayerProfile and ProfileConstraints as this is how the profile values are returned for PlayFab:

{
  login: {
    infoRequestParameters: {
      GetPlayerProfile: true,
      ProfileConstraints: {
        ShowDisplayName: true,
        ShowAvatarUrl: true,
      },
      //...
    },
    //...
  },
  //...
}

If there is a value set for login.extendedProfileKey in config, then the extended profile data is retrieved from the player's user data. The easiest way to do this is to set the login.infoRequestParameters values for GetUserData and UserDataKeys. The value for UserDataKeys should include an entry matching the value for login.extendedProfileKey:

{
  login: {
    extendedProfileKey: 'extendedProfile',
    infoRequestParameters: {
      GetUserData: true,
      UserDataKeys: ['extendedProfile'],
      //...
    },
    //...
  },
  //...
}

If login.extendedProfileKey is set but login.infoRequestParameters is not set to return the extended profile data, a separate call to $playfab.getUserData will be made.

Login Status

At the end of the login process, the Jovo session value of playfab.loginStatus will be set to 'newUser', 'existingUser', or 'error'.

Session Data

Sample session data for a new player:

{
  "playfab": {
    "profile": {
      "displayName": "Blue-eyed Albatross 7769",
      "avatarUrl": "https://example.com/avatar4.png",
      "extendedProfile": {
        "color": "#3a2af7",
        "locale": "en"
      }
    },
    "loginStatus": "newUser",
    "isDisplayNameUpdated": true,
    "loginInfo": {
      "SessionTicket": "6DDA124D5...-AAA...",
      "PlayFabId": "6DDA124D...",
      "NewlyCreated": true,
      "SettingsForUser": {
        "NeedsAttribution": false,
        "GatherDeviceInfo": true,
        "GatherFocusInfo": true
      },
      "InfoResultPayload": {
        "UserInventory": [],
        "UserData": {},
        "UserDataVersion": 0,
        "UserReadOnlyDataVersion": 0,
        "CharacterInventories": [],
        "PlayerStatistics": []
      },
      "EntityToken": {
        "EntityToken": "NHxZM0N...",
        "TokenExpiration": "2022-08-14T16:32:50.729Z",
        "Entity": {
          "Id": "BA1D4E2...",
          "Type": "title_player_account",
          "TypeString": "title_player_account"
        }
      },
      "TreatmentAssignment": {
        "Variants": [],
        "Variables": []
      }
    }
  }
}

Sample session data for an existing player:

{
  "playfab": {
    "profile": {
      "displayName": "Blue-eyed Albatross 7769",
      "avatarUrl": "https://example.com/avatar4.png",
      "extendedProfile": {
        "color": "#3a2af7",
        "locale": "en"
      }
    },
    "loginStatus": "existingUser",
    "loginInfo": {
      "SessionTicket": "6DDA124D5...-AAA...",
      "PlayFabId": "6DDA124D...",
      "NewlyCreated": false,
      "SettingsForUser": {
        "NeedsAttribution": false,
        "GatherDeviceInfo": true,
        "GatherFocusInfo": true
      },
      "LastLoginTime": "2022-08-13T16:32:50.729Z",
      "InfoResultPayload": {
        "UserInventory": [],
        "UserData": {
          "extendedProfile": {
            "Value": "{\"color\":\"#3a2af7\",\"locale\":\"en\"}",
            "LastUpdated": "2022-08-13T16:32:51.644Z",
            "Permission": "Public"
          }
        },
        "UserDataVersion": 1,
        "UserReadOnlyDataVersion": 0,
        "CharacterInventories": [],
        "PlayerStatistics": [],
        "PlayerProfile": {
          "PublisherId": "F54412...",
          "TitleId": "B729",
          "PlayerId": "6DDA124D...",
          "DisplayName": "Blue-eyed Albatross 7769",
          "AvatarUrl": "https://example.com/avatar4.png"
        }
      },
      "EntityToken": {
        "EntityToken": "NHxZM0N...",
        "TokenExpiration": "2022-08-14T16:43:26.323Z",
        "Entity": {
          "Id": "BA1D4E2...",
          "Type": "title_player_account",
          "TypeString": "title_player_account"
        }
      },
      "TreatmentAssignment": {
        "Variants": [],
        "Variables": []
      }
    }
  }
}

The value of playfab.loginInfo is a snapshot of the values at login time. They are most useful when processing the first request in a session. The Jovo session data playfab.loginInfo.PlayFabId value is useful in many PlayFab API calls so it has been added to this.$playfab.PlayFabId.

Use the profile info in code: this.$session.data.playfab.profile.

$playfab.updateProfile

Call updateProfile() to create a profile if setting the profile failed during login() (check this.$session.data.playfab.isDisplayNameUpdated) or update the existig profile. Returns true if the profile was updated. Otherwise, false. Since each property on a profile is a separate call to PlayFab, there may be a situation where one fails and another succeeds. If only one succeeds, the return value is true.

await this.$playfab.updateProfile({ displayName: 'my name' });

The profile has these values:

interface ProfileInfo {
  displayName?: string;
  avatarUrl?: string;
  extendedProfile?: unknown;
}
  • displayName: Optional. The player's display name. For PlayFab the max length is 25 characters.
  • avatarUrl: Optional. The URL of the player's avatar.
  • extendedProfile: Optional. A single object with string keys and any value. This value is stored in PlayFab user data only when the login.extendedProfileKey config entry has a value.

You can set displayName, avatarUrl and extendedProfile combined or separately. The extendedProfile value is all set together.

$playfab.updateStat

Statistics in PlayFab have a string key and a number value (ex: score: 10). Call updateStat() to update a statistic:

await this.$playfab.updateStat('score', 10);

$playfab.getStat

Statistics in PlayFab have a string key and a number value (ex: score: 10). Call getStat() to get a statistic:

const score = await this.$playfab.getStat('score');

$playfab.getLeaderboard

A leaderboard in PlayFab is related to a statistic and can have a frequency of hourly, daily, weekly, monthly or manually. PlayFab automatically handles creating a new board and keeping a history.

See leaderboard for configuration settings.

Call getLeaderboard() to get the active (current version) leaderboard:

const leaderboard = await this.$playfab.getLeaderboard('score');

The result has the following structure:

{
    DisplayName?: string;
    PlayFabId?: string;
    Position: number;
    Profile?: PlayerProfileModel;
    StatValue: number;
    IsCurrentPlayer: boolean;
    UserData?: any
}

The values Position, StatValue and IsCurrentPlayer are the only values that will always be set. Other values are determined by configuration settings and if the players have the values set in PlayFab.

Here is a sample result:

[
  {
    "PlayFabId": "28309...",
    "DisplayName": "Defiant Gerbil 0210",
    "StatValue": 100,
    "Position": 0,
    "Profile": {
      "PublisherId": "F5441...",
      "TitleId": "A999",
      "PlayerId": "28309...",
      "DisplayName": "Defiant Gerbil 0210"
    },
    "IsCurrentPlayer": false,
    "UserData": {}
  },
  //...
  {
    "PlayFabId": "6DDA124D...",
    "DisplayName": "Blue-eyed Albatross 7769",
    "StatValue": 0,
    "Position": 6,
    "Profile": {
      "PublisherId": "F5441...",
      "TitleId": "A999",
      "PlayerId": "6DDA124D...",
      "DisplayName": "Blue-eyed Albatross 7769",
      "AvatarUrl": "https://example.com/avatar4.png"
    },
    "IsCurrentPlayer": true,
    "UserData": {
      "extendedProfile": {
        "color": "#3a2af7",
        "locale": "en"
      }
    }
  }
]

$playfab.updateUserData

Call updateUserData() to set user data for the current player. Pass the data and an optional permission (default: Public). Returns true if the data was updates. Otherwise, false.

const result = await this.$playfab.updateUserData({ key1: 'value1' });

The data is a single object with string keys and any values. Since PlayFab stores all user data values as strings, each property value is passed to JSON.stringify() before the API is called. Doing this makes the call easier to use and allows for objects and arrays as property values.

NOTE: There are limits for user data values. Refer to Title Settings > Limits in Game Manager (the PlayFab developer portal).

$playfab.getUserData

Call getUserData() to get public user data for the current player or another player. Returns an object with user data properties or null;

Pass a string or string[] to keys for the user data values you want returned. When playFabId is not passed, values for the current user are returned. You will not normally need to set playFabId, but getLeaderboard() uses it when returning extended profile user data.

const result = await this.$playfab.getUserData({ keys: 'key1' });

Since PlayFab stores all user data values as strings, this function attempts to convert each property value back to a string, number, boolean, object or array.

NOTE: There are limits for user data values. Refer to Title Settings > Limits in Game Manager (the PlayFab developer portal).

Server API

Most of the functions on this.$playfab wrap one or more calls to the Server API and groups them together in a simplified, useful way.

To access all the functionality of the Server API, call this.$playfab.PlayFabServer followed by the request you want to make. For more information, see PlayFab Server REST API in the documentation.

To turn the callback function into a promise, use promisify.

Here is an example:

import { promisify } from 'util';

const getUserData = promisify(this.$playfab.PlayFabServer.GetUserData)
const result = await getUserData({/*...*/});

To use the Server API, you must set the value of developerSecretKey in the plugin config. This sets PlayFab.settings.developerSecretKey when the plugin loads.

Admin API

Calling this.$playfab.updateProfile() and updating the display name uses the Admin API.

To access all the functionality of the Admin API, call this.$playfab.PlayFabAdmin followed by the request you want to make. For more information, see PlayFab Admin REST API in the documentation.

To turn the callback function into a promise, use promisify.

Here is an example:

import { promisify } from 'util';

const updateUserTitleDisplayName = promisify(this.$playfab.PlayFabAdmin.UpdateUserTitleDisplayName)
const result = await updateUserTitleDisplayName({/*...*/});

To use the Admin API, you must set the value of developerSecretKey in the plugin config. This sets PlayFab.settings.developerSecretKey when the plugin loads.

Jovo Debugger

If using the Jovo Debugger, you must add $playfab to the list of properties the debugger ignores:

// app.dev.ts

new JovoDebugger({
  ignoredProperties: ['$app', '$handleRequest', '$platform', '$playfab'],
}),

Jovo Game Starter project

To get started with PlayFab in Jovo, check out the jovo-game-starter project.

Videos

License

MIT