Appearance
Appearance
The subscription system provides an easy way to efficiently fetch data from the game server and share it between dashboard components. One Subscription Option maps to one data source (i.e. one API endpoint) from the game server. There are many pre-defined Subscription Options to make it easy to fetch the most commonly used types of data. If you create your own API endpoints, you might also want to create your own Subscription Options for them.
It's easy to subscribe to data. Here we'll subscribe to the game server's backend status API:
import { useSubscription } from '@metaplay/subscriptions'
import { getBackendStatusSubscriptionOptions } from '@metaplay/core'
const {
data: statusData,
} = useSubscription(() => getBackendStatusSubscriptionOptions())
Notice how we subscribe to the data using useSubscription()
. We pass in an anonymous function that returns a SubscriptionOptions
object from getBackendStatusSubscriptionOptions()
. This is a pattern that we use to subscribe to all data sources. You'll find pre-configured subscriptions to most game server endpoints inside the src/subscription_configs
folder of the core SDK module.
INFO
Note on Vue's lifecycles: We automatically set up an onUmounted
hook to unsubscribe from the subscription when the component is destroyed. While handy, this requires subscriptions to be initialized during the setup
function of a component.
Some Subscription Options need arguments to know what URL or query parameters to subscribe to. For example, if you want to subscribe to information about an individual player, the getSinglePlayerSubscriptionOptions()
function needs to know the player's ID:
import { useSubscription } from '@metaplay/subscriptions'
import { getSinglePlayerSubscriptionOptions } from '@metaplay/core'
const props = defineProps<{
playerId: string
}>()
const {
data: playerData,
} = useSubscription(() => getSinglePlayerSubscriptionOptions(props.playerId))
There are a couple of key things to note here:
playerId
property is passed to getSinglePlayerSubscriptionOptions()
to get the Subscription Options for this specific player.playerId
is changed to point to a different player then playerData
will also be changed to return this new player's data.When you call the useSubscription()
function, you are returned a SubscriptionObject
. This object has the following fields:
// We can destructure the fields from the return value like this:
const {
/**
* A reactive reference to the data returned by this subscription.
* This starts out as undefined and changes once the data has been fetched.
*/
data,
/**
* An error object if there was an error fetching the data.
* Defaults to undefined.
*/
error,
/**
* A boolean value that tells you whether the current user
* has permission to access this subscription.
*/
hasPermission,
/**
* A function that you can call to refresh the data immediately.
*/
refresh,
} = useSubscription(getBackendStatusSubscriptionOptions())
You'll already be familiar with data
from the example above. But what about the others?
error
contains an Error
object if there was an error when fetching the data. This will most likely be a 401 error (did you make a call to an endpoint that you don't have permission to reach?) or a 404 (did you request an endpoint that doesn't exist?).hasPermission
is a boolean value that lets you know whether the current user has the relevant permissions for this subscription. See below for more details on permissions.refresh
is a function that you can call to refresh the data immediately. This is useful if you have a slow polling subscription, but you know that the data has become stale.You can also define your own re-usable Subscription Options if you've added game-specific API endpoints. You can simply copy one of our core Subscription Options as an example and modify it to suit your needs.
For example, if you wanted to create a subscription that fetched the data for a single player every 5 seconds and kept it in the cache for 5 minutes, you could do something like this:
import {
type SubscriptionOptions,
getFetcherPolicyGet,
getCacheRetentionPolicyKeepForever,
getCacheRetentionPolicyTimed,
getPollingPolicyTimer,
} from '@metaplay/subscriptions'
// Define the shape of the data that will be returned by this subscription.
// This is just an example, you should replace this with the actual properties.
type CustomPlayerData = {
name: string
score: number
}
// Note how we pass in the CustomPlayerData type hint to the SubscriptionOptions so that the return data is correctly typed.
export function getCustomPlayerSubscriptionOptions (playerId: string): SubscriptionOptions<CustomPlayerData> {
return {
// The user permission, if any, required to access the underlying data.
permission: 'api.players.view',
// How often is new data fetched?
pollingPolicy: getPollingPolicyTimer(5000), // 5000ms, or 5 seconds
// How is the data fetched?
fetcherPolicy: getFetcherPolicyGet(`/players/${playerId}`),
// How long is the data kept in the cache after polling has stopped?
cacheRetentionPolicy: getCacheRetentionPolicyTimed(300000), // 300000ms, or 5 minutes
}
}
A SubscriptionOptions
object tells the subscription system how to fetch data from the server and what to do with it once it arrives.
export interface SubscriptionOptions {
/**
* Defines when to poll for new data from the server.
*/
pollingPolicy: PollingPolicy
/**
* Defines how to fetch data for this subscription form the server.
*/
fetcherPolicy: FetcherPolicy,
/**
* Optional: Data mutation function for this subscription.
*/
dataMutator?: DataMutatorCallback
/**
* Optional: The permission required for the data fetch to succeed.
* If user does not have this permission then we can fail early and skip the fetch.
*/
permission?: string
/**
* Cache retention policy (when to clear old data)
* for when there are no more subscribers.
*/
cacheRetentionPolicy: CacheRetentionPolicy
}
As discussed above, the subscription system automatically watches the subscription options and re-subscribes to the new data source if they change. There is a small amount of overhead associated with this. The overhead is so small that you normally don't need to worry about it, but you may notice in some of our core Dashboard code that we sometimes use a slightly different way to subscribe to data:
import { useSubscription } from '@metaplay/subscriptions'
import { getBackendStatusSubscriptionOptions } from '@metaplay/core'
const {
data: statusData,
} = useSubscription(getBackendStatusSubscriptionOptions())
Note the small difference here - we pass in a SubscriptionOptions
object directly, whereas previously we've passed in a function that returns a SubscriptionOptions
object. Doing it this way means that the subscription object cannot watch for changes, which means that it can use a slightly optimized code path. And that's fine here because we know that getBackendStatusSubscriptionOptions
will never change.
This isn't something that you need to worry about, but now you know why it's there should you spot it during a dive into our core code.