Enhanced Experiences (E2) SDK Guide
Introduction
The Twitch Enhanced Experiences SDK is a library that provides access to Twitch’s Enhanced Experiences Service through an API targeted at client applications used by Twitch broadcasters and server applications used by game companies.
There are three versions of the Enhanced Experiences SDK:
- The C++ version is for native use on Windows, Xbox One, and PlayStation 4
- The C# version is for use in any .NET environment, including Unity
- The TypeScript version is for use on any platform supporting Node
This document describes all versions. Most of the classes and methods are similar between the versions, so familiarity with one will help you learn the others. For the first two, there are several library files, both static and dynamic, for these platforms in both debug and release builds.
The audience for this document is game developers. Game developers use the Enhanced Experiences SDK to send data to the Enhanced Experiences Service. The Enhanced Experiences Service, in turn, propagates the data to clients, such as Twitch Discovery and Twitch Extensions.
The Enhanced Experiences SDK
There is one class in the Enhanced Experiences SDK, the DataSource
class. It will perform all of the Enhanced Experiences Service connection handling, error handling, and rate limiting. The class has one child structure, the Configuration
structure. It contains all of the data necessary to begin an Enhanced Experiences Service session with a DataSource
object. The usage of these types is the same for all supported platforms.
Connecting to the Enhanced Experiences Service
The DataSource
class is in one of two states, connected or not connected. All newly-constructed DataSource
objects start in the not connected state. In this state, the only method that will succeed is the Connect
method. All other methods will throw a TwitchException
object. In a debug build, there will be a message stating there is no active connection. In Visual Studio, such messages appear in the Output pane.
If the Connect
method succeeds, the object is in the connected state. In this state, if given valid parameters, all other methods will succeed but the Connect
method will throw a TwitchException
object. If you want your application to reconnect, it must first disconnect by invoking the Disconnect
method to transition to the not connected state. If the Connect
method fails, for example, due to a transient network problem, your application may invoke it again to retry the connection.
To invoke the Connect
method, first construct a Configuration
object to hold the connection parameters. Populate the object with the necessary fields. Note that all fields must have a value. The debugFn
, initialValue
, and timeout
fields have default values but you may change them to values you find more suitable to your needs. There are six required fields in a Configuration
object:
token
broadcasterIds
gameId
enviroment
onTokenExpired
initialData
The first required field of a Configuration
object is the token
field. This holds a Twitch OAuth token from Twitch Identity. See the Twitch Authentication documentation for more information. You can use the Authentication SDK or any other valid, supported means to get this token. There are two types of acceptable tokens, user access tokens and app access tokens. Which type you need depends on your runtime environment.
In a client environment, such as in a game application running on a user’s desktop, the token must be a user access token. In this case, do not populate the broadcasterIds
field since the Enhanced Experiences Service will determine the broadcaster from the token. If you populate this field, the Enhanced Experiences Service will reject the connection.
In a server environment, such as in a back-end service, the token must be an app access token. In this case, you must populate the broadcasterIds
field as described in the following paragraph or the Enhanced Experiences Service will reject the connection.
The second required field of a Configuration
object is the broadcasterIds
field. The Enhanced Experiences Service uses this to determine which broadcasters to associate with the data it receives from the DataSource
object. As stated previously, do not specify this field in a client environment. In a server environment, populate this field with the list of broadcaster identifiers associated with the data you provide to the DataSource
object. Note that you must obtain permission from those broadcasters to send their data.
The third required field of a Configuration
object is the gameId
field. The Enhanced Experiences Service uses this to determine which game to associate with the data it receives from the DataSource
object. If the game identifier is not valid, the Connect
method will not fail but the Enhanced Experiences Service will reject all messages. In a debug build, the Enhanced Experiences SDK will print messages describing the errors.
The fourth required field of a Configuration
object is the environment
field. This field has two acceptable values, prod
and dev
. Use prod
if you are in production. The Enhanced Experiences Service will propagate data to all subscribed clients. Use dev
if you are in development or testing. The Enhanced Experiences Service will accept the data but will not propagate it.
The fifth required field of a Configuration
object is the onTokenExpired
field. This is a call-back the Enhanced Experiences SDK will use in case the provided token is invalid or expires. This function’s only parameter is a call-back the Enhanced Experiences SDK will provide for your application to invoke when it has acquired a token. This function’s return value is a Boolean. Return true
if your application will invoke the provided call-back or false
if your application will not, perhaps because it is shutting down or a user has canceled the operation.
The sixth required field of a Configuration
object is the initialData
field. This is a string containing the JSON representation of your data object. For example, if your object expressed in JavaScript is {one: 1, two: ['a', 'b']}, the string to use to set this field is "{\"one\":1,\"two\":[\"a\",\"b\"]}"
. The Enhanced Experiences Service uses this to create its view of your data. The other methods operate on this data.
There are two optional fields in a Configuration object:
- timeout
- debugFn
The timeout
field has a default value of ten seconds. Your application may specify a different time-out to suit your purpose. If your application specifies zero, there will be no time-out.
The debugFn
field has a default value, specific to the language, that prints to the console. Your application may specify its own function if it wants to perform a different action with the arguments, such as keeping a log of all debug activity.
If the Connect
method succeeds, it will return a session identifier. If your application provided a non-empty value for the sessionId
field of the Configuration
object, this will be that value. Otherwise, it will be a unique value the DataSource
object creates for you. If the Connect
method fails, it will throw a TwitchException
object.
Updating the Enhanced Experiences Service
The DataSource
class has four methods to update the view the Enhanced Experiences Service has of your data object. They are described in more detail, with examples, in the API Reference. As mentioned above, all of these methods throw a TwitchException
object if the DataSource
object is not in the connected state.
Three of the methods take a path
parameter. The value of this parameter is the same as what you might type in JavaScript to access a particular property of your data object. For instance, suppose a data object has the following structure.
{
"foo": {
"bar": [
{
"baz": 1
}
]
}
}
A path of "foo.bar[0].baz"
refers to the field whose value is 1
. A path of "foo.bar[0]"
refers to the first element of the bar
array, the object containing the baz
field. A path of "foo.bar"
refers to the entire array. A path of "foo"
refers to the object containing the bar
field.
There are restrictions on what constitutes a valid value for the path
parameter. It may not be empty. It must parse as a valid JavaScript path. The methods that have a path
parameter have additional restrictions.
The path
parameter of the RemoveField
method must specify a field that is in the object. It may not specify an array element. The Enhanced Experiences Service does not allow removing array elements. The only way to do so is to update the array field with a new array value.
The path
parameter of the UpdateField
method must specify either a field or array element that is in the object or a new field whose parent is in the object. For instance, given the above data object, a path
of "foo.bar[0].baz"
will update the current baz
field value while a path
of "foo.baz"
will add a new baz
field to the foo
object. Providing a path
of "foo.baz.bar"
will cause the UpdateField
method to throw a TwitchException
object. You may use this method to modify array elements but not to modify arrays. For instance, a path
of "foo.bar[0]"
will update the first element of the bar
array while a path
of "foo.bar[1]"
or a path
of "foo.baz[0]"
will cause the UpdateField
method to throw a TwitchException
object.
The path
parameter of the AppendToArrayField
method must specify a field or array element that is in the object. That field or array element must be an array. The value
parameter of the AppendToArrayField
method must be a JSON representation of an array. For instance, given the above data object, a path
of "foo.bar"
and a value of "[null,3,\"four\"]"
will update the data object as follows.
{
"foo": {
"bar": [
{
"baz": 1
},
null,
3,
"four"
]
}
}
There are function templates you can use to perform the task of converting your data into a JSON string representation. There are function templates for all primitive types and all C++ container types. It is also possible to provide arbitrary structures of data. For instance, consider the following structure.
struct MyStruct {
unsigned u;
std::array<uint8_t, 9> a;
};
You may pass an instance of this structure as the second parameter of the UpdateField
method if you provide an implementation of the Stringify
function for it, such as follows.
void Stringify(std::ostream& os, MyStruct const& m) {
Twitch::StringifyObject(os, _T("u"), m.u, _T("a"), m.a);
}
Note that due to pointer decay you must declare arrays using std::array
if you want to use this mechanism. Also, due to a computer’s inherent imprecision representing real numbers, invoking UpdateField
with a value of, say, 9.9f
will result in a JSON string similar to "9.89999961853027"
.
Disconnecting from the Enhanced Experiences Service
To disconnect from the Enhanced Experiences Service, invoke the Disconnect
method of the DataSource
object. You need not invoke this method if you are destroying the DataSource
object; it is invoked automatically in this case.
C++
SDK Types
All types in the Enhanced Experiences SDK are in the Twitch
namespace.
Aliases
Several C++ built-in types and standard library classes have aliases (typedef
s) since their types can vary based on the character type (i.e., char
or wchar_t
).
On Windows-based platforms (i.e., Windows and Xbox One), the Enhanced Experiences SDK assumes the use of Unicode. In this case, the string types and types dependent on those strings (e.g., std::wstringstream
) use wide (16-bit) characters.
On other platforms (e.g., PS4), those types use single-byte (8-bit) characters.
The common header file, BaseSDK.h
, contains the aliases, notably:
string_t
(aliasingchar const*
orwchar_t const*
)tstring
(aliasingstd::string
orstd::wstring
)
Threading
Several parts of the Enhanced Experiences SDK run asynchronously due to long-running network activities. To ensure clients are in control of the execution of their applications, the Enhanced Experiences SDK uses C++ std::future
objects to provide asynchronous execution. This enables three different threading modes, described below.
Client Multi-Threading
In this mode, the client has multiple active threads and controls all threads.
Methods returning std::future
objects create such objects with std::async
, with a launch policy of std::launch::deferred
. This means the part of the invoked method that runs asynchronously will not run until the client invokes either the get
or wait
method of the returned std::future
object. To prevent blocking the main thread, the client will invoke the get
or wait
method on a separate thread. This provides the greatest control over the timing of the deferred execution, since the client thread chooses when to invoke the get
or wait
method.
If an exception occurs during the deferred invocation, the get
method throws that exception.
The client is responsible for synchronizing access to data shared among any threads it creates.
SDK Multi-Threading
In this mode, the client uses only one thread directly but is aware of and accounts for additional threads.
The client wraps methods returning std::future
objects with Callback
objects, providing call-back functions to those objects. Such objects invoke the call-back functions passed to them on a thread different from the one which invoked the method which created the Callback
object, since the Callback
objects run the asynchronous part of such methods on a thread launched with std::async
with a launch policy of std::launch::async
.
The client is responsible for synchronizing access to data shared among the call-back thread, the invoking thread, and any other threads the client creates.
The lifetime of a Callback
object must encompass the entire activity, so do not destroy a Callback
object until its associated call-back function has run.
Single Threading
In this mode, the client has one thread and performs no data synchronization.
The client wraps methods returning std::future
objects with Pollable
objects. These objects create internal threads. Such threads are not exposed to the client and do not access client data, so no synchronization is required.
Clients running a “game loop,” such as an update-render loop, can check the IsReady
property of the Pollable
object to determine if the result of the operation is available. When it is true
, the client can use the other properties of the Pollable
object to get the value (if the method was successful) or the exception (if it was not). All such methods are guaranteed to return quickly, making them suitable for use in a game loop.
The lifetime of a Pollable
object must encompass the entire activity. In other words, do not destroy a Pollable
object until its IsReady
property returns true
.
Methods returning void
do not create internal threads. If they have long-running components, the object will provide properties to check the status (e.g., IsReady
) and get the result (e.g., Value
), much like the Pollable
objects.
C#
SDK Types
All types in the Enhanced Experiences SDK are in the Twitch
namespace.
Threading
As of Unity 2018.4, Unity defaults to version 4.0 of the .NET Framework. As a result, it is now possible to use asynchronous code by employing the async
and await
keywords and Task<T>
return type in C#. This implementation makes use of this feature instead of using Unity’s coroutines. All asynchronous methods have Async
as a suffix on their names.
JSON
The Enhanced Experiences SDK uses but does not come with the Newtonsoft.Json
library. There is a third-party version for Unity you can include in your Unity projects.
TypeScript
The TypeScript implementation has all of the methods described in the Enhanced Experiences Service section above but uses a different mechanism for creating an instance of the interface to access the Enhanced Experiences Service. The default export of the Node module is a creator function, createDataSource
, that creates the object implementing the IDataSource
interface.
The TypeScript implementation has an additional method not included in the other versions of the Enhanced Experiences SDK, updateData
. This method accepts a data object and automatically generates the differences between it and the previous data object and sends those differences to the Enhanced Experiences Service in its expected format. This means you may use this method instead of the other methods if it is more convenient for you to do so.
Another difference between the implementation described above and the TypeScript implementation is the exceptions the methods might throw. In the TypeScript implementation, all exceptions are of type Error
. Also, since Node is a memory-managed environment, there is no destructor. This means your application must invoke the disconnect
method when it is done communicating with the Enhanced Experiences Service. In all other respects, the usage of the Enhanced Experiences SDK is the same as that described above.