Skip to content

AsyncNode Plugin

The AsyncNode Plugin is used to enable streaming additional content into a flow that has already been loaded and rendered.
A common use case for this plugin is conversational UI, as the users input more dialogue, new content must be streamed into Player in order to keep the UI up to date.

The pillar that makes this possible is the concept of an AsyncNode. An AsyncNode is any tree node with the property async: true, it represents a placeholder node that will be replaced by a concrete node in the future.

In the example below, node with the id “some-async-node” will not be rendered on first render, but will be replaced with a UI asset node at a later time:

{
"id": "flow-1",
"views": [
{
"id": 'action',
"actions": [
{
"id": "some-async-node",
"async": true,
},
],
},
],
...
}

The AsyncNodePlugin exposes an onAsyncNode hook on all platforms. The onAsyncNode hook will be invoked with the current node when the plugin is available and an AsyncNode is detected during the resolve process. The node used to call the hook with could contain metadata according to content spec.

User should tap into the onAsyncNode hook to examine the node’s metadata before making a decision on what to replace the async node with. The return could be a single asset node or an array of asset nodes, or the return could be even a null|undefined if the async node is no longer relevant.

Returning a value in the above context enables uses cases where the async node only needs to be resolved once. For use cases where the async node needs to be updated multiple times, the onAsyncNode hook provides a second callback argument that can be used to update the value multiple times. For example, if the async node is used to represent some placeholder for toasts, or notifications, the async node handler could initially resolve with some content, and then update with null after some time to remove those views.

Continuous Streaming

In order to keep streaming in new content, there must be at least 1 or more AsyncNodes in the view tree at all times.
This means there must be a constant renewal of new AsyncNodes after the previous ones are resolved by the user.

Usage

The AsyncNodePlugin itself accepts an options object with a plugins array, enabling the integration of multiple view plugins for extended functionality. The AsyncNodePluginPlugin is provided as a default way of handling asset-async nodes, it is just one handler for one possible way of using async nodes. If the default behavior does not align with the desired usage, users are able to provide their own implementation of the handler in the form of a plugin to be passed to the base AsyncNodePlugin. The AsyncNodePluginPlugin also comes from the '@player-ui/async-node-plugin' and contains the resolver and parser functionality.

Add the plugin to Player:

import { Player } from '@player-ui/player';
import { AsyncNodePlugin, AsyncNodePluginPlugin } from '@player-ui/async-node-plugin';
const asyncNodePlugin = new AsyncNodePlugin({
plugins: [new AsyncNodePluginPlugin()],
});
// Configuring async node behaviour
asyncNodePlugin.hooks.onAsyncNode.tap('handleAsync', async (node: Node.Node) => {
...
// Determine what to return to be parsed into a concrete UI asset
});
// For use cases where the async node needs to be updated multiple times
asyncNodePlugin.hooks.onAsyncNode.tap("toast-provider", async (node: Node.Async, update: (content) => void) => {
...
// do some async task to get content
const toastContent = await makeToastFor(node.id);
// set timer for 5 seconds to remove the toast content from the view
setTimeout(() => update(null), 5000);
// uses same mechanism as before
return toastContent;
});
const player = new Player({
plugins: [
asyncNodePlugin
]
})