forio Toggle navigation

Push Channel API

The Push Channel API allows you to publish and subscribe to messages within a project, group, or world.

Channel notifications are only available for team projects. To enable notifications for your project, update your project settings to turn on the Push Channel setting, and optionally enable authorization for the channel.

There are two main use cases for the push channel: event notifications and chat messages.

Event Notifications

Within a multiplayer simulation or world, it is often useful for your project's model to alert the user interface (browser) that something new has happened.

Usually, this "something new" is an event within the project, group, or world, such as:

  • An end user comes online (logs in) or goes offline. (This is especially interesting in a multiplayer world; only available if you have enabled authorization for the channel.)
  • An end user is assigned to a world.
  • An end user updates a variable / makes a decision.
  • An end user creates or updates data stored in the Data API.
  • An operation (method) is called. (This is especially interesting if the model is advanced, for instance, the Vensim step operation is called.)

When these events occur, you often want to have the user interface for one or more end users automatically update with new information.

These event notifications occur on a cometd channel.

Chat Messages

Another reason to use the push channel is to allow players (end users) to send chat messages to other players, and to have those messages appear immediately.

You can send these messages via the cometd channel or via the REST API. We recommend the cometd channel as the browser is already using it to listen for event notifications.

Getting Started

First, enable channel notifications for your project:

  • Channel notifications are only available for team projects. To enable notifications for your project, update your project settings to turn on the Push Channel setting, and optionally require authorization for the channel.

Next, make sure you understand:

Then, see specifics on:

Finally, see it all in action:

Push Channel Message Infrastructure

Protocols and Libraries

In general the Epicenter push channel message infrastructure uses cometd, specifically the Bayeaux protocol and the broadcast channel.

Epicenter supports cometd version 3 with the acknowledgement and timesync extensions. Epicenter uses the WebSocket protocol by default, with a fallback to long-poll transport. See the complete cometd documentation.

Additionally, the Epicenter push channel message infrastructure allows publishing via a REST API.

At Forio, we typically use the cometd JavaScript library for our own projects.

The diagram shows this basic infrastructure:

  • Events within the model (model service) trigger automatic publishing to the push channel.
  • Events of your choosing can be published to the push channel, either via cometd or REST.
  • All push notifications, whether automatically published or published to a specific channel via cometd or REST, are sent to the browser.

Message Structure

Each message is a JSON object, whether using cometd or REST. This JSON object includes three top-level fields:

  • type
  • subtype
  • data

The type and subtype identify the kind of action or data that prompted this message. The subtype is optional.

The type can be: run, data, user, world, or chat.

  • The run type can have a subtype of new, update, variables, operation, delete.
  • The data type can have a subtype of new, update, delete.
  • The user type can have a subtype of connect, disconnect.
  • The world type can have a subtype of assign, unassign, assignchange.
  • The chat type does not require a subtype.

The data is an object that includes both the message itself and metadata about the message (e.g. timestamp, user id). The exact structure of the data object varies based on the message type.

For example:

{
    "type": "data",
    "subType": "update",
    "data": {
        "path": "/7d05c0a9-dc79-11e4-bf91-56847afe9799/item",
        "data": "updated testData"
    },
    "date": "2015-04-06T16:59:15.861+0000"
}

Subscribing (Registering & Listening) to Messages on a Particular Push Channel

In order to subscribe (register and listen) to messages posted to a particular push channel, you'll need to use the cometd JavaScript library, which includes automatic support for the Bayeaux protocol. Epicenter supports cometd version 3 with the acknowledgement and timesync extensions. Epicenter uses the WebSocket protocol by default, with a fallback to long-poll transport. (See more information under Infrastructure, above.)

To include the cometd library and extensions:

<script type="text/javascript" src="jquery/jquery-1.11.0.js"></script>
<script type="text/javascript" src="org/cometd.js"></script>
<script type="text/javascript" src="org/cometd/AckExtension.js"></script>
<script type="text/javascript" src="org/cometd/TimeSyncExtension.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd-ack.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd-timesync.js"></script>

See the cometd JavaScript library for background or simply download the .js files to include. Epicenter supports cometd version 3.

To configure a push channel:

Once you have included the library: create a push channel (that is, a cometd instance), configure it with the Epicenter channel URL (https://api.forio.com/v2/channel/subscribe), complete the handshake, and listen to the meta channels so you can tell whether you are properly connected.

var cometd = $.cometd;
cometd.configure({ url: 'https://api.forio.com/v2/channel/subscribe' });

cometd.addListener('/meta/handshake', function(message) { 
    if (message.successful === true)
        { console.log('ok to register channels'); }
});

cometd.addListener('/meta/connect', function(message) {
    if (message.successful === true
        { console.log('connected'); }
});

cometd.handshake();

IMPORTANT: If you have required Push Channel Authorization, you need to pass the user name and authorization details in an extension (ext) object as part of the handshake:

var userName = "user1";
var authorization = "Bearer eyJhbGciOiJSUzI1NiJ9"; // the user access token or project access token
cometd.handshake({
    ext: {
        userName: userName,
        authorization: authorization
    }
});

Even if you have not required Push Channel Authorization, you can still send the user name. For example, this allows the group and world channels to automatically receive messages when the user comes online.

To subscribe to a push channel:

Once you have completed the handshake, subscribe to a channel.

cometd.subscribe('<channel>', function(message) {...});

Epicenter provides some channels for you. If you are using an already-defined channel, the <channel> must be one of:

  • /project/{account id}/{project id}
  • /group/{account id}/{project id}/{group name}
  • /world/{account id}/{project id}/{group name}/{world id}
  • /user/{account id}/{project id}/{group name}/{user id}
  • /data/{account id}/{project id}/{collection id}

You can also add your own channels. If you have required Push Channel Authorization, then your <channel> must be one of the list above, or a custom channel in the form:

  • /general/{account id}/{project id}/{other path you define...}
  • /chat/{account id}/{project id}/{other path you define...}

If your project does NOT require Push Channel Authorization, then <channel> can be any name.

The account id is the Team ID. The project id is the Project ID.

The group name is the alphanumeric string you chose to identify the group, for example "fall2014-seminar". You can find group names in the Epicenter user interface by navigating to your team project home page (Dashboard > [Team Name] > [Project Name]), and selecting Groups from the left.

The user id is the system unique identifier for the end user. You can find user ids for end users in this group by searching for them with the User API.

The world id is the system unique identifier for the world. You can find world ids for this group by searching for them with the Multiplayer World API.

The collection id is set or specified during initial creation of data in a project using the Data API.

The function(message) {...} is an optional callback function. The message.data contains the body of the message (in JSON). Push channels to which you are subscribed automatically receive messages, approximately every second.

For example, to print out the message data, you might use:

cometd.subscribe('/group/acme-simulations/supply-chain-game/fall-seminar/', function (message) { 
    $('#chat').append('<div>New message: ' +
        JSON.stringify(message.data) + '</div>');
});

Automatic Publishing of Events

Events are automatically published to group, world, and data channels for subscribers.

Currently, no event messages are automatically published to the project channel or the user channel.

Automatic Messages to the Group Channel

For projects with groups of end users, there are several messages which are automatically sent to the group channel. The data field of the mssage sent varies based on the event that triggered the message.

These events include:

  • End user comes online (logs in) in this project and group.

  • End user goes offline in this project and group.

Automatic Messages to the World Channel

For multiplayer worlds and the runs associated with them, there are several messages which are automatically sent to the world channel. (All subscribers to each push channel receive posted messages approximately every second.) The data field of the message sent varies based on the event that triggered the message.

These events include:

  • New run created for this world.

    • The data object is the run record and a date (timestamp).
  • End user assigned to this world.

    • The data object is the array of all users currently in the world.
    • The subType is assign.
  • End user unassigned from this world.

    • The data object is the array of users unassigned from the world.
    • The subType is unassign.
  • End user's assignment is updated within this world, for example, the end user's role is changed.

    • The data object is the array of updated users.
    • The subType is assignchange.
  • End user comes online (logs in) to this project, group, and world.

  • End user goes offline in this project, group, and world.

  • Operations (methods) called for this run.

    • The data object includes the operation (with name and result), the user id of the user that called the operation, the run record, and a date (timestamp).
  • Variables updated for this run.

    • The data object includes the variables updated, the user id of the user that updated the variables, the run record, and a date (timestamp).

Automatic Messages to the Data Channel

If end users have specifically subscribed to the data channel for a collection in this project, there are several messages which are automatically sent to the data channel. (All subscribers to each push channel receive posted messages approximately every second.) The subType and data fields of the message sent vary based on the event that triggered the message.

These events include:

  • Data created in this collection.

    • For a Data API create (POST) request, the subtype is new and the data object includes the path (to the item within the data collection) and an object with the data itself.
    • Example Data API request:

        curl -X POST \
            'https://api.forio.com/v2/data/acme-simulations/supply-chain-game/coll1' \
            --header 'Content-Type: application/json' \
            --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
            --data '{"item": "testData"}'
      
    • Message body provided to subscribers:

        {
            "type": "data",
            "subType": "new",
            "data": {
                "path": "/7d05c0a9-dc79-11e4-bf91-56847afe9799",
                "data": {
                    "id": "7d05c0a9-dc79-11e4-bf91-56847afe9799",
                    "lastModified": "2015-04-06T16:58:53.506Z",
                    "item": "testData"
                }
            },
            "date": "2015-04-06T16:58:53.554+0000"
        }
      
  • Data updated in this collection.

    • For a Data API update (PUT) request, the subtype is update and the data object includes the path (to the item within the data collection) and the changed data.
    • Example Data API request:

        curl -X PUT \
            'https://api.forio.com/v2/data/acme-simulations/supply-chain-game/coll1/7d05c0a9-dc79-11e4-bf91-56847afe9799/item' \
            --header 'Content-Type: application/json' \
            --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
            --data '"updated testData"'
      
    • Message body provided to subscribers:

        {
            "type": "data",
            "subType": "update",
            "data": {
                "path": "/7d05c0a9-dc79-11e4-bf91-56847afe9799/item",
                "data": "updated testData"
            },
            "date": "2015-04-06T16:59:15.861+0000"
        }
      
  • Data deleted from this collection.

    • For a Data API delete (DELETE) request, the subtype is delete and the data object includes only the path (to the item within the data collection) to the deleted data.
    • Example Data API request:

        curl -X DELETE \
            'https://api.forio.com/v2/data/acme-simulations/supply-chain-game/coll1/7d05c0a9-dc79-11e4-bf91-56847afe9799' \
            --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
      
    • Message body provided to subscribers:

        {
            "type": "data",
            "subType": "delete",
            "data": {
                "path": "/7d05c0a9-dc79-11e4-bf91-56847afe9799"
            },
            "date": "2015-04-06T17:09:31.923+0000"
        }
      

Publishing a Message to a Push Channel via the cometd JavaScript Library

You can publish a message to a push channel with the cometd JavaScript library. All subscribers to each push channel receive posted messages approximately every second.

At Forio, we typically use the cometd JavaScript library for our own projects.

To include the cometd library and extensions:

<script type="text/javascript" src="jquery/jquery-1.11.0.js"></script>
<script type="text/javascript" src="org/cometd.js"></script>
<script type="text/javascript" src="org/cometd/AckExtension.js"></script>
<script type="text/javascript" src="org/cometd/TimeSyncExtension.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd-ack.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd-timesync.js"></script>

See the cometd JavaScript library for background or simply download the .js files to include. Epicenter supports cometd version 3.

To configure a push channel:

Once you have included the library: create a push channel (that is, a cometd instance), configure it with the Epicenter channel URL (https://api.forio.com/v2/channel/subscribe), complete the handshake, and listen to the meta channels so you can tell whether you are properly connected.

var cometd = $.cometd;
cometd.configure({ url: 'https://api.forio.com/v2/channel/subscribe' });

cometd.addListener('/meta/handshake', function(message) { 
    if (message.successful === true)
        { console.log('ok to register channels'); }
});

cometd.addListener('/meta/connect', function(message) {
    if (message.successful === true
        { console.log('connected'); }
});

cometd.handshake();

IMPORTANT: If you have required Push Channel Authorization, you need to pass the user name and authorization details in an extension (ext) object as part of the handshake:

var userName = "user1";
var authorization = "Bearer myToken"; // the user access token or project access token
cometd.handshake({
    ext: {
        userName: userName,
        authorization: authorization
    }
});

Even if you have not required Push Channel Authorization, you can still send the user name. For example, this allows the group and world channels to automatically receive messages when the user comes online.

To publish a message:

Once you have completed the handshake, publish your message:

cometd.publish(<channel>, message);

Epicenter provides some channels for you. If you are using an already-defined channel, the <channel> must be one of:

  • /project/{account id}/{project id}
  • /group/{account id}/{project id}/{group name}
  • /world/{account id}/{project id}/{group name}/{world id}
  • /user/{account id}/{project id}/{group name}/{user id}
  • /data/{account id}/{project id}/{collection id}

You can also add your own channels. If you have required Push Channel Authorization, then for your channels, <channel> must be one of:

  • /general/{account id}/{project id}/{other path you define...}
  • /chat/{account id}/{project id}/{other path you define...}

If your project is NOT requiring Push Channel Authorization, then <channel> can be any name.

The account id is the Team ID. The project id is the Project ID.

The group name is the alphanumeric string you chose to identify the group, for example "fall2014-seminar". You can find group names in the Epicenter user interface by navigating to your team project home page (Dashboard > [Team Name] > [Project Name]), and selecting Groups from the left.

The user id is the system unique identifier for the end user. You can find user ids for end users in this group by searching for them with the User API.

The world id is the system unique identifier for the world. You can find world ids for this group by searching for them with the Multiplayer World API.

The collection id is set or specified during initial creation of data in a project using the Data API.

The message is the JSON object of the message.

This JSON object includes three fields:

  • type
  • subtype
  • data

The type and subtype identify the kind of action or data that prompted this message. The subtype is optional. The data is an object with both the message itself and metadata about the message (e.g. timestamp, user id). The exact structure of the data object varies based on the message, and you can create your own formats for different kinds of messages based on the kinds of messages that are typically sent in your project. See Message Structure, above, for additional details.

Publishing a Message to a Push Channel via the REST API

You can publish a message to a push channel with the Push Channel API POST method (RESTful). All subscribers to each push channel receive posted messages approximately every second.

Publishing via REST API is less common than publishing via cometd JavaScript library. We recommend the cometd channel as the browser is already using it to listen for event notifications.


Method: POST

URI:

  • Publish to a project:
    • /channel/publish/project/{account id}/{project id}
  • Publish to a group:
    • /channel/publish/group/{account id}/{project id}/{group name}
  • Publish to a world:
    • /channel/publish/world/{account id}/{project id}/{group name}/{world id}
  • Publish to a user:
    • /channel/publish/user/{account id}/{project id}/{group name}/{user id}
  • Publish to your own channel:
    • /general/{account id}/{project id}/{other path you define...}
    • /chat/{account id}/{project id}/{other path you define...}

Headers:

  • Content-Type: application/json
  • Authorization: Bearer{project access token}

Body: JSON object including three fields: type, subtype, and data. The type and subtype identify the kind of action or data that prompted this message. The subtype is optional.

Return Status: 200 (successful response)

Return Body: JSON object including whether the message was successfully sent and the specific push channel on which the message was sent and received.


Example:

curl -X POST \
    'https://api.forio.com/v2/channel/publish/world/acme-simulations/supply-chain-game/fall2014-seminar/5453d6ddfac2a40207000005' \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"type": "chat", "data": "A chat message welcoming end users to this world."}'    

Example Response:

{
   "message": "Message sent.",
   "publishUrl": "https://api.forio.com/v2/channel/publish",
   "cometUrl": "https://api.forio.com/v2/channel/subscribe",
   "channel": "/world/acme-simulations/supply-chain-game/fall2014-seminar/5453d6ddfac2a40207000005"
}

Notes:

  • You can add your own channels. If you have enabled Push Channel Authorization, then for your channels, the URI can also be one of:

    • /general/{account id}/{project id}/{other path you define...}
    • /chat/{account id}/{project id}/{other path you define...}
  • If your project is NOT requiring Push Channel Authorization, then the URI can also be any named channel of your creation.

  • In the URI, the Account ID is the Team ID for team projects or the User ID for personal projects. (Note that worlds and groups are only available for team projects.)

  • In the URI, the Group Name is the alphanumeric string you chose to identify the group, for example "fall2014-seminar". You can find group names in the Epicenter user interface by navigating to your team project home page (Dashboard > [Team Name] > [Project Name]), and selecting Groups from the left. (Note that this field is called Group ID when you are creating the group.)

  • In the request body, the type and subtype identify the kind of action or data that prompted this message. The data field includes the content of the message.

    The type can be: run, data, user, or chat.

    • The run type can have subtype: new, update, variables, operation, delete.
    • The data type can have subtype: new, update, delete.
    • The user type can have subtype: connect, disconnect.
    • The chat type does not require a subtype.

      See Message Structure, above, for additional details.

  • The response includes the following fields:

    • message: whether the message was successfully sent.
    • publishUrl: where the message was published.
    • cometUrl: where the message was received.
    • channel: the specific push channel on which the message was sent and received.

Subscribe and Publish Example

See the complete example HTML for subscribing and publishing to a group channel, from scratch or using Epicenter.js.

Group Channel Example: From Scratch

Here is a complete example HTML file that subscribes and publishes to a group channel (based on the group name that you enter), using the code from the above sections on subscribing and publishing.

The example hard codes the team id (acme-simulations) and project id (supply-chain-game), but you could also enter those, or take them from the URL of the published project.

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <script type="text/javascript" src="jquery/jquery-1.11.0.js"></script>
    <script type="text/javascript" src="org/cometd.js"></script>
    <script type="text/javascript" src="org/cometd/AckExtension.js"></script>
    <script type="text/javascript" src="org/cometd/TimeSyncExtension.js"></script>
    <script type="text/javascript" src="jquery/jquery.cometd.js"></script>
    <script type="text/javascript" src="jquery/jquery.cometd-ack.js"></script>
    <script type="text/javascript" src="jquery/jquery.cometd-timesync.js"></script>

    <script>

    (function($) {

        var cometd = $.cometd;

        $(document).ready(function() {

            // Disconnect when the page unloads
            $(window).unload(function() { cometd.disconnect(true); });

            cometd.configure({ url: 'https://api.forio.com/v2/channel/subscribe' });

            cometd.addListener('/meta/handshake', function(handshake) {
                if (handshake.successful === true) {
                    $('#output').append('<div>You may now register channels.</div>');
                }
            });

            cometd.addListener('/meta/connect', function(message) {
                if (message.successful === true) { console.log('Connected'); }
            });

            // would need to pass in userName and authorization here,
            // if this project required push channel authorization
            cometd.handshake();

            function registerChannel(channel) {
                cometd.subscribe(channel, function(message) {
                    $('#output').append('<div>In channel ' + channel + 
                        ', new message: ' + JSON.stringify(message.data) + '</div>');
                });

                $('#output').append('<div>Registered channel: ' + channel + '.</div>');
            }

            function publish(channel, message) {
                 cometd.publish(channel, message);
            }

            // use /group because this is a group channel
            // for other types of channels, use other prefixes
            $('#registerGroupChannel').click(function() {
                registerChannel('/group/acme-simulations/supply-chain-game/' + $('#group').val());
            });

            // for some use cases,
            // you may require the messageBody to be JSON
            // so you could add error checking for valid JSON here
            $('#channelSendButton').click(function() {
                publish('/group/acme-simulations/supply-chain-game/' + $('#groupName').val(), 
                    $('#messageBody').val());
            });
        });

    })(jQuery);

    </script>

    <title>Basic Channel Example</title>
</head>

<body>

    <p>Group Name: <input id="group" value="" /></p>

    <p>Click to subscribe to channel for named group: 
        <input type="button" value="Register Group Channel" id="registerGroupChannel"/> 
    </p>

    <p>Send message to named group: </p>
        <textarea id="messageBody"></textarea><br>
        <input id="channelSendButton" type="button" value="Send Message"/>

    <p>Output:</p>

    <div id="output"></div>

</body>
</html>

Group Channel Example: Using Epicenter.js

Here is a complete example HTML file that subscribes and publishes to a group channel (based on the group name that you enter), using the Epicenter Channel Manager that is part of the Epicenter.js library. (See more on the stack available for creating your user interface.)

<html>
<head>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="//forio.com/tools/js-libs/2.1.0/epicenter.js"></script>
    <script src="//forio.com/tools/js-libs/2.1.0/epicenter-multiplayer-dependencies.js">
    </script>

    <script>

    (function($) {

      $(document).ready(function() {

        var authMgr = new F.manager.AuthManager({
              // account and project are determined automatically
              // from the URL of the project
              // userName, password, and groupId can be set by hand
              // or can be taken e.g. from the login component
              // see https://forio.com/epicenter/docs/public/api_adapters/#components
              userName: 'user2',
              password: 'passw0rd',
              groupId: 'b0315d47-fb61-46bc-ad14-5e418638bd12'
        });

        authMgr.login().then( function() {

          var cm = new F.manager.ChannelManager();
          var gc = cm.getGroupChannel();

          // this user is subscribing to the top-level group channel
          // for the group they are logged in to
          gc.subscribe('', function(message) { 
            $('#output').append('<div>New message: ' + JSON.stringify(message.data) + '</div>');
          });

          $('#channelSendButton').click(function() { gc.publish('', $('#messageBody').val()); });

        });

      });

    })(jQuery);

  </script>

  <title> Basic Channel Example, with Epicenter.js </title>
</head>

<body>

    <p>Send message to group: </p>
        <textarea id="messageBody"></textarea><br>
        <input id="channelSendButton" type="button" value="Send Message"/>

    <p>Output:</p>

    <div id="output"></div>

</body>
</html>