Messaging Guide
chevron down
 

Messaging Guide

Overview

The Messaging API allows developers to easily send simple messages between the app and its 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 open 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 * as messaging from "messaging";

messaging.peerSocket.addEventListener("open", (evt) => {
  console.log("Ready to send or receive messages");
});

Error Handling

The error 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 open 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.

messaging.peerSocket.addEventListener("error", (err) => {
  console.error(`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 * as messaging from "messaging";

messaging.peerSocket.addEventListener("open", (evt) => {
  sendMessage();
});

messaging.peerSocket.addEventListener("error", (err) => {
  console.error(`Connection error: ${err.code} - ${err.message}`);
});

function sendMessage() {
  // Sample data
  const 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 message event.

import * as messaging from "messaging";

messaging.peerSocket.addEventListener("message", (evt) => {
  console.error(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 bufferedamountdecrease 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
let 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++);
    }
  }
}

messaging.peerSocket.addEventListener("bufferedamountdecrease", (evt) => {
  sendMoreData();
});

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 * as messaging from "messaging";

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

function processWeatherData(data) {
  console.log(`The temperature is: ${data.temperature}`);
}

messaging.peerSocket.addEventListener("open", (evt) => {
  fetchWeather();
});

messaging.peerSocket.addEventListener("message", (evt) => {
  if (evt.data) {
    processWeatherData(evt.data);
  }
});

messaging.peerSocket.addEventListener("error", (err) => {
  console.error(`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 * 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";

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.error(`Error fetching weather: ${err}`);
  });
}

function returnWeatherData(data) {
  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    messaging.peerSocket.send(data);
  } else {
    console.error("Error: Connection is not open");
  }
}

messaging.peerSocket.addEventListener("message", (evt) => {
  if (evt.data && evt.data.command === "weather") {
    queryOpenWeather();
  }
});

messaging.peerSocket.addEventListener("error", (err) => {
  console.error(`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 open 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.