Getting started /w Livenote 🔥
REST API
Please check our API v1 or API v2 documentation. We will supply you with a permanent bearer token to consume the API.
Example usage Pusher client
In our client side application React we have a WebsocketBuddy service which we use to connect with Pusher. The following examples contain redacted and/or modified code for you to get an idea of how to get started.
Environment variables
We're using environment variables in a dotenv file, .env:
PUSHER_APP_KEY=pusher-key
PUSHER_APP_CLUSTER=pusher-cluster
We will supply the correct credentials.
Code
For the intents of the purposes of this example, the file is located at services/websockets/index.js.
import Pusher from 'pusher-js'
const PusherInstance = (channelId, listeners) => {
// Initiate Pusher (see: https://pusher.com/docs/channels/using_channels/client-api-overview/)
const pusher = new Pusher(process.env.PUSHER_APP_KEY, {
cluster: process.env.PUSHER_APP_CLUSTER,
})
// Subscribe to Pusher channel (see: https://pusher.com/docs/channels/using_channels/public-channels/)
const channel = pusher.subscribe(channelId)
// Bind to channel events
Object.keys(listeners).forEach((listener) => {
const callback = listeners[listener]
if ('function' !== typeof callback) {
throw new Error (`Listener callback must be a function, but got ${typeof callback}`)
}
// Bind (see https://pusher.com/docs/channels/using_channels/events/#binding-on-the-channel)
channel.bind(listener, callback)
})
return { pusher, channel }
}
export const WebsocketBuddy = ({
channel = null,
driver = null,
listeners = {},
}) => {
if (!channel) {
throw new Error(`Channel is required`)
}
if (!driver) {
throw new Error(`Driver is required`)
}
switch (driver) {
case 'Pusher':
return PusherInstance(channel, listeners)
default:
throw new Error(`Driver ${driver} not supported`)
}
}
Subscribing to Pusher channel
We're using a container/component pattern with Redux-Saga, because our application is quite large – and growing. You probably won't need to follow our code examples to a tee, but for good measure we'll include everything so you can pick and choose how to go about using Pusher.
Form container
import Form from 'pages/Form'
import { connect } from 'react-redux'
import {
REQUEST_GET_GAME,
REQUEST_GET_GAME_CLOCK,
REQUEST_GET_GAME_EVENTS,
REQUEST_GET_GAME_SCORE,
} from 'redux/actions/game'
const mapStateToProps = (state, { id, sportSlug }) => ({
id,
sportSlug,
clock: state.game.clock,
game: state.game.form,
events: state.game.events,
score: state.game.score,
})
const mapDispatchToProps = (dispatch, { id, sportSlug }) => ({
get: () => {
// API call: https://data.livenote.nl/api/v1/documentation#/Game/game.get
dispatch({ type: REQUEST_GET_GAME, id })
// API call: https://data.livenote.nl/api/v1/documentation#/Game/clock.get
dispatch({ type: REQUEST_GET_GAME_CLOCK, id })
// API call: https://data.livenote.nl/api/v1/documentation#/Game/events.get
dispatch({ type: REQUEST_GET_GAME_EVENTS, id })
// API call: https://data.livenote.nl/api/v1/documentation#/Game/score.get
dispatch({ type: REQUEST_GET_GAME_SCORE, id })
},
})
export default connect(mapStateToProps, mapDispatchToProps)(Form)
Form component
Then, in our form we import the WebsocketBuddy service and initiate it as follows:
import React, { useEffect } from 'react'
import { WebsocketBuddy } from 'services/websockets'
const Form = ({
id,
clock,
events,
game,
get,
score,
sportSlug,
}) => {
useEffect(() => {
// On page (re)load, get data from API
get()
// Subscribe to Pusher for realtime updates
const { channel, pusher } = WebsocketBuddy({
driver: 'Pusher',
channel: `${sportSlug}-${id}`,
listeners: {
clockUpdated: ({ message }) => {
console.log(message) // See docs below for description of object
},
}
});
return function cleanUp() {
// Unbind all handlers
channel.unbind()
// Disconnect from Pusher
pusher.disconnect()
}
}, [])
// Do cool stuff with clock, events, game and score props
return (
<div>Cool form component 😎</div>
)
}
export default Form
Pusher messages
With the coding out of the way, lets quickly run over the different types of messages your application can expect
clockUpdated event
| property | description | type | values |
|---|---|---|---|
| type | type of event | string | start_first_quarter, end_first_quarter, start_second_quarter, end_second_quarter, start_third_quarter, end_third_quarter, start_fourth_quarter, end_fourth_quarter, pause or resume |
| direction | whether clock counts up or down. | string | down or up |
| trigger | whether to start or stop the clock | string | start or stop |
| delta | which time (in seconds) to set on the clock | int |
clockUpdated message example
{
type: "start_first_quarter",
direction: "down",
trigger: "start",
delta: 900
}
Setting clock delta
With the exception of pause and resume event types, all events signify a fixed point in the game which can be used as a (re)calibration point for the clock and should be considered safe.
The pause and resume events occur during game play. If for some reason the graphics overlay application is out of synch, updating the clock time from the delta property on pause and resume type events, could have some weird side effects. It's probably best to simply either start or stop the clock without updating the clock time when these events occur.
matchUpdated event
| property | description | type | values |
|---|---|---|---|
| type | type of event | string | bully |
| timestamp | pure game time of event in seconds | int |
matchUpdated message example
{
type: "bully",
timestamp: 850
}
scoreUpdated event
| property | description | type | values |
|---|---|---|---|
| type | type of event | string | goal, goal_penalty_corner, goal_penalty_stroke, goal_shoothout |
| team_id | scoring team | uuid string | |
| player_id | scoring player (if available) | uuid string | |
| score | update score for team | int |
scoreUpdated message example
{
type: "goal",
team_id: "8cfab860-2aef-4f62-b503-abb8ed3f11fa",
player_id: "ab38d444-ff55-41ab-8f4b-015d08c33293",
score: 1
}
timelineUpdated event
| property | description | type | values |
|---|---|---|---|
| type | type of event | string | yellow_card, green_card, long_corner, foul, red_card, penalty_corner, penalty_stroke, substitution_in, substitution_out, free_hit |
| team_id | team | uuid string | |
| player_id | player (if available) | uuid string | |
| timestamp | pure game time of event in seconds | int |
timelineUpdated message example
{
type: "foul",
team_id: "8cfab860-2aef-4f62-b503-abb8ed3f11fa",
player_id: "ab38d444-ff55-41ab-8f4b-015d08c33293",
timestamp: 850
}