Johnson Controls Streaming API
Your Metasys system works continuously: sensors report current conditions, algorithms make adjustments, and alarms alert operators to potential issues. Streaming is how the Metasys API provides access to these _events_ using the HTML standard [server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) (SSE). For further information, refer to [Mozilla developer guide](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). In this document, _Streaming_ typically refers to the Metasys feature and _stream_ refers to the concept of the HTTP connection and the flow of data. Per the standard, a server-sent event usually contains three components, separated by newline, with the last component having a second newline to mark the end of the event. ```text id: \n event: \n data: \n\n ``` | Line Prefix | Explanation | | ----------- | ------------------------------------------------------------------- | | `id` | ordering id to enable the "pick up where you left off" reconnection | | `event` | message type | | `data` | message payload as a JSON string | Example: ```text id: b6d184ee-e137-44aa-b9b1-14bc704cd25d:1\n event: hello\n data: b6d184ee-e137-44aa-b9b1-14bc704cd25d\n\n ``` ## Getting Started To get started, you first need a client. There are SSE clients available in many languages. Begin by [getting a stream](#operation/getStream). Only one stream is needed regardless of the number of subscriptions you later create. As explained in the documentation for getting a stream, the first event, `hello`, is a special event because the `data` field contains your _stream ID_. Some operations support the `METASYS-SUBSCRIBE` header which transforms them from being a single request/response operations into a streaming subscription. The value of the header is the _stream ID_ from the `data` field of the `hello` event described above. As an example, refer to the [Get Activities](#operation/getActivities) operation for more details about this technique. This technique does not apply to every operation. See each operation's description to understand its Streaming capability. Not all _event_ data is currently exposed by Streaming. Future versions of the API will add further Streaming capabilities. ## Streaming Connection Maintenance There are a few behaviors to be aware of if you want your stream to work beyond the lifetime of the access token used to [get your stream](#operation/getStream). ### Keep Events Flowing After Initial Access Token Expiration Recall that your access token has an expiration time. To keep the stream longer than the lifetime of that access token, be sure to [refresh your token](#operation/getToken) just as you would when using non-Streaming portions of the API. Not only will you get a fresh token to use, but internally, your stream expirations are updated to match the expiration time of the new token. ### Reconnect and Using the Current Access Token in Reconnect Requests The server-sent events standard has a provision for reconnecting a lost connection. Call the same [get a stream](#operation/getStream) operation but also add the `Last-Event-Id` header with the `id` of the last event received. Most clients handle this for you. Metasys continues to buffer events for at least thirty seconds after the connection failure is detected, so you can resume streams that experienced brief outages. If Metasys has cleaned up your buffer or cannot find that `id` in the recently sent buffer, you will get a 204 response per the SSE specification. Because your existing stream is not resumable, your `Last-Event-Id` is no longer valid. The recovery path is to start anew by calling [get a stream](#operation/getStream) without the `Last-Event-Id` and subscribe again to data of interest. One critical aspect of reconnect is ensuring that the reconnect request is done with the current access token. As covered by [get a stream](#operation/getStream), Streaming lets you specify the token in either the `Authorization` header or query string. This flexibility accommodates a variety of clients. For example, the web browser client, `EventSource`, does not provide access to request headers, so you have to use the query string. For `EventSource`, this however poses an issue because the reconnect attempt will be done against the original URL which has a potentially outdated access token embedded in the query string. For that reason, if you want to use a JavaScript client, consider using the open source [EventSource component](https://github.com/EventSource/eventsource). The Metasys User Interface (MUI) employs this library to workaround the limitation of the browser's `EventSource` . When using it, use the `Authorization` header instead. Keep the options object passed to the constructor so you can update the `Authorization` property when you refresh your access token. Assuming: 1. Your access token is in variable `accessToken` 1. Variable `streamingUrl` is the URL for [get a stream](#operation/getStream) ```javascript const eventSourceOptions = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const eventSource = new $window.EventSourcePolyfill( streamingUrl, eventSourceOptions ); ``` Before token expiration: 1. Get a [refreshed token](#tag/authentication/operation/getToken). Behind the scenes this also refreshes you stream expirations. 1. Modify `eventSourceOptions` with new token like example below ```javascript eventSourceOptions.headers.Authorization = `Bearer ${newToken}`; ``` ### Tips For Other Client Libraries SSE clients exist in many languages. Inclusion in this section is not an endorsement; however, if you chose one of these clients, these tips may prove useful for ensuring the current token is used for reconnect. #### C# - 3ventic EventSource When using the [3ventic EventSource for C#](https://github.com/3ventic/EvtSource) you will need to use Reflection to update `Uri` which is a private field within the EventSource that contains the location to [get a stream](#operation/getStream) with the access token embedded in the query string. ```csharp public static void SetField(Object obj, String fieldName, object value) { Type type = obj.GetType(); FieldInfo field = type.GetField( fieldName, BindingFlags.NonPublic | BindingFlags.Instance ); field.SetValue(obj, value); } // example usage Reflector.SetField(eventSource, "Uri", uriWithCurrentToken); ``` #### C# - LaunchDarkly EventSource When using the [LaunchDarkly EventSource for C#](https://github.com/launchdarkly/dotnet-eventsource) you will not call the `Reset` method to attempt a reconnect. Instead, dispose of the EventSource and then create a new instance, passing the the `LastEventId` from the last message and the URL to [get a stream](#operation/getStream) with a current access token embedded as a query string parameter. ## Streaming Events The following are types of events sent by Streaming along with example `data` payloads for each: - `hello` - Indicates Streaming connection was successful. Example Data Payload ```json "b6d184ee-e137-44aa-b9b1-14bc704cd25d" ``` - `heartbeat` - Indicates Streaming connection is alive. Example Data Payload ```json "2021-02-16T16:21:10.23Z" ```