Messaging Guide
chevron down
 

Messaging Guide

Overview

The Messaging API allows developers to easily send simple messages between the app and it's companion using a synchronous socket based communications channel. This API has an identical implementation in the Device API and the Companion API, so code examples work the same in both locations.

This API is a subset of the WebSocket API.

How It Works

The application and companion are commonly referred to as "peers". Each peer must successfully open a MessageSocket connection before any messages can be sent. Connections are established automatically, and the onopen event will be emitted upon connection.

Once a connection has been established, peers may send and receive simple messages. For example, the device peer can ask the companion peer to request data from a web service, then the companion can send that response to the device.

The data is automatically encoded using CBOR before it is sent. This allows developers to easily send and receive the following JavaScript data types:

  • Number
  • String
  • ArrayBuffer or ArrayBufferView
  • true, false, null, undefined
  • Array Object [1,2]
  • Object {"a": "1"}

Opening a Connection

A connection must be successfully opened before any messages can be sent or received.

// Import the messaging module
import * as messaging from "messaging";

// Listen for the onopen event
messaging.peerSocket.onopen = function() {
  // Ready to send or receive messages
}

Error Handling

The onerror event will be emitted whenever the peer connection is lost, this usually means that one or more messages have been lost. You will receive another onopen event before the connection can be used again. You should always check the readyState of the connection before attempting to send a message.

Some connectivity issues may be temporary, like the device going out of Bluetooth range, but others may be more fatal, like the mobile device running out of battery.

// Listen for the onerror event
messaging.peerSocket.onerror = function(err) {
  // Handle any errors
  console.log("Connection error: " + err.code + " - " + err.message);
}

Sending a Message

Once a connection has been established, you can send a message to the connected peer.

// Import the messaging module
import * as messaging from "messaging";

// Listen for the onopen event
messaging.peerSocket.onopen = function() {
  // Ready to send messages
  sendMessage();
}

// Listen for the onerror event
messaging.peerSocket.onerror = function(err) {
  // Handle any errors
  console.log("Connection error: " + err.code + " - " + err.message);
}

// Send a message to the peer
function sendMessage() {
  // Sample data
  var data = {
    title: 'My test data',
    isTest: true,
    records: [1, 2, 3, 4]
  }

  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    // Send the data to peer as a message
    messaging.peerSocket.send(data);
  }
}

Receiving a Message

To receive a message from the connected peer, simply subscribe to the onmessage event.

// Import the messaging module
import * as messaging from "messaging";

// Listen for the onmessage event
messaging.peerSocket.onmessage = function(evt) {
  // Output the message to the console
  console.log(JSON.stringify(evt.data));
}

Checking Connection State

The Messaging API provides a method to check the state of the current connection. You can use the readyState property to confirm that the connection is OPEN before sending a message, or display an error if the connection is CLOSED.

if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
  // Send a message
}
if (messaging.peerSocket.readyState === messaging.peerSocket.CLOSED) {
  // Display error message
}

Maximum Message Size

It is important to note that the maximum size of a single serialized message is 1027 bytes (MAX_MESSAGE_SIZE). This means that due to serialization, the largest blob of data using Uint8Array is 1024 bytes. Sending a message which exceeds this size limit will trigger an exception.

If you need to send messages which exceed the maximum size, you would need to split the messages into multiple parts before sending, or alternatively, use the File Transfer API.

Buffering Data

When sending multiple messages, it is important to avoid overflowing the buffer. The actual buffer size will vary depending upon the application's available memory. The bufferedAmount property can be used to determine how much data currently resides within the buffer. By subscribing to the onbufferedamountdecrease event, the sending of data can be resumed once there is available space in the buffer.

In a maximum throughput application, developers can pre-fill the buffer with a few messages in advance. Or, in a minimal latency application, developers can wait for the buffer to be empty, in order to send the most recent data.

// Counter used to limit the amount of messages sent
var counter = 0;

function sendMoreData() {
  // Artificial limit of 100 messages
  if (counter < 100) {
    // Send data only while the buffer contains less than 128 bytes
    while (messaging.peerSocket.bufferedAmount < 128) {
      // Send the incremented counter value as the message
      messaging.peerSocket.send(counter++);
    }
  }
}

// Listen for the onbufferedamountdecrease event
messaging.peerSocket.onbufferedamountdecrease = function() {
  // Amount of buffered data has decreased, continue sending data
  sendMoreData();
}

// Start sending data
sendMoreData();

Fetching Weather Example

In this example, the device will send a message to the companion to request the current temperature for San Francisco. The companion will query the Open Weather API, then send the data back to the device.

The OpenWeather API requires an API key. See here for more information.

Device JavaScript

The following JavaScript runs on the device. When the application is launched and the MessageSocket is opened, a message is sent to the companion requesting weather data. The application will then wait to receive the weather data from the companion. Every 30 minutes after launch, it will request weather data again.

// Import the messaging module
import * as messaging from "messaging";

// Request weather data from the companion
function fetchWeather() {
  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    // Send a command to the companion
    messaging.peerSocket.send({
      command: 'weather'
    });
  }
}

// Display the weather data received from the companion
function processWeatherData(data) {
  console.log("The temperature is: " + data.temperature);
}

// Listen for the onopen event
messaging.peerSocket.onopen = function() {
  // Fetch weather when the connection opens
  fetchWeather();
}

// Listen for messages from the companion
messaging.peerSocket.onmessage = function(evt) {
  if (evt.data) {
    processWeatherData(evt.data);
  }
}

// Listen for the onerror event
messaging.peerSocket.onerror = function(err) {
  // Handle any errors
  console.log("Connection error: " + err.code + " - " + err.message);
}

// Fetch the weather every 30 minutes
setInterval(fetchWeather, 30 * 1000 * 60);

Companion JavaScript

The following JavaScript runs on the companion. The companion will wait for a command from the device, then query the Open Weather API. The weather data is then sent to the device.

// Import the messaging module
import * as messaging from "messaging";

var API_KEY = "your-key-goes-here";
var ENDPOINT = "https://api.openweathermap.org/data/2.5/weather" +
                  "?q=San%20Francisco,USA&units=imperial";

// Fetch the weather from OpenWeather
function queryOpenWeather() {
  fetch(ENDPOINT + "&APPID=" + API_KEY)
  .then(function (response) {
      response.json()
      .then(function(data) {
        // We just want the current temperature
        var weather = {
          temperature: data["main"]["temp"]
        }
        // Send the weather data to the device
        returnWeatherData(weather);
      });
  })
  .catch(function (err) {
    console.log("Error fetching weather: " + err);
  });
}

// Send the weather data to the device
function returnWeatherData(data) {
  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    // Send a command to the device
    messaging.peerSocket.send(data);
  } else {
    console.log("Error: Connection is not open");
  }
}

// Listen for messages from the device
messaging.peerSocket.onmessage = function(evt) {
  if (evt.data && evt.data.command == "weather") {
    // The device requested weather data
    queryOpenWeather();
  }
}

// Listen for the onerror event
messaging.peerSocket.onerror = function(err) {
  // Handle any errors
  console.log("Connection error: " + err.code + " - " + err.message);
}

If you're interested in retrieving weather data for the current location, you'll want to read our Geolocation guide.

Best Practices

  1. Use the onopen event to trigger a message when the connection is opened.
  2. Don't exceed the maximum message size (MAX_MESSAGE_SIZE).
  3. Implement buffering when sending multiple messages.
  4. You can use the readyState property to check the connection state.

Messaging in Action

If you're interested in using the Messaging API within your application, why not check out the "Bay Area Rapid Transit (BART)" example app or review the Messaging API reference documentation.