GraphQL Subscriptions

GraphQL Subscriptions enable clients to listen to real-time messages whenever important changes happen inside Bluescape workspaces and organizations. The client connects to the server with a bi-directional communication channel using the WebSocket protocol and sends a subscription query that specifies which event it is interested in. When an event is triggered, the Bluescape server executes the stored GraphQL query, and the result is sent back to the client using the same communication channel.

The client can unsubscribe by sending a message to the Bluescape server. The Bluescape server can also unsubscribe at any time due to errors or timeouts.

List of Subscriptions

You can access the playground via the GraphQL Playground page. Click the Docs link on the far right border and look for the list of currently available subscriptions in the SUBSCRIPTIONS section at the bottom of the column.

Example of implementation

Content in this page:

Example of subscription implementation

Below you can find 2 types of implementations: using the GraphQl Playground to test GraphQL and inspect the subscriptions, and using a script to capture and process the subscription events.

Subscription implementation in GraphQL Playground

You can access the playground via the GraphQL Playground page. Here is an example of a subscription for events, for reporting the change in position (x,y) of Image elements, using the commands subscription:

subscription imageElementUpdated($workspaceId: String!) {
  commands(workspaceId: $workspaceId) {
    ... on UpdateElementCommand {
      elementId
      workspaceId

      data {
        ... on UpdateImage {
          transform {
            x
            y
          }
        }
      }
    }
  }
}

Set the correct values for:

  • QUERY VARIABLES:
{
    "workspaceId": "<workspaceId>"
}
  • and HTTP HEADERS
{
  "Authorization":"Bearer <SET_TOKEN>"
}

Click the Play button in the top middle section of the screen. You should see a Listening… message at the bottom of the right panel in the Playground. Interact with an image in the workspace to change its position.
After moving the image, a new event is reported to the subscription, where the position (x,y) has changed:

Subscription implementation in a NodeJs script

The implementation in the GraphQL Playground shows us how the data for the subscription is delivered. The next step is to use a script or an implementation to take specific actions based on the data of the events that we are receiving from the subscription.

Inspect the examples below to implement a basic parser for the subscription events.

/*
  To install, add the following dependencies to your package.json:
  {
    "name": "ws-client-demo",
    "version": "1.0.0",
    "type": "module",
    "license": "MIT",
    "dependencies": {
      "graphql": "^15.5.0",
      "graphql-ws": "^4.2.1",
      "ws": "^7.4.4"
    }
  }
  
  After creating the package.json, install the dependencies:
  npm install
*/

import ws from 'ws';
import {createClient} from 'graphql-ws';
const JWT_TOKEN = '<SET_TOKEN>';

// Please note the 'wss' protocol
const WS_URL = 'wss://api.apps.us.bluescape.com/v3/graphql';
const WORKSPACE_ID = '<workspaceId>';

const observer = {
  error(e) {
    console.log('Observer.error', e);
  },
  next(eventData) {
    console.log('Observer.next');
    console.dir(eventData, {depth: null});
    // Here you can inspect and process the incoming eventData to take specific actions 
  },
  complete() {
    console.log('Observer.complete');
  }
}
function subscribe(query, variables) {
  const client = createClient({
    url: WS_URL,
    webSocketImpl: ws,
    connectionParams: () => {
      return {
        Authorization: `Bearer ${JWT_TOKEN}`
      }
    },
  });
  client.subscribe({query, variables}, observer);
}

// Set the subscription
const GRAPHQL_SUBSCRIPTION = `subscription imageChangesSubscription($workspaceId: String!) {
    commands(workspaceId: $workspaceId) {
      ... on UpdateElementCommand {
        workspaceId
        elementId
        data {
          ... on UpdateImage {
            transform {
              x
              y
            }
          }
        }
      }
    }
  }`

subscribe( GRAPHQL_SUBSCRIPTION, {
    workspaceId: WORKSPACE_ID
  });

When you move an image, this is the type of event you receive (the value of the id field is just an example):

 Update received:
{
  data: {
    commands: {
      workspaceId: '<workspaceId>',
      elementId: '609abX787e69f2ca25b65262',
      data: { transform: { x: 13599.666666666668, y: -8254.500000000004 } }
    }
  }
}

You can parse this even data and trigger an action.

Example of use of subscriptions: check ingestion status of uploads

Another interesting case for the use of subscriptions is to monitor the ingestion status of the upload of images, documents, or videos.

The upload of assets to the workspace will go through 3 ingestion stages:

  • transfering
  • processing
  • complete_success

You can use the example above and replace the graphQL subscription with the one below:

subscription viewUploadAssetIngestionStateSubscription($workspaceId: String!) {
    item: commands(workspaceId: $workspaceId) {
      ... on UploadAssetCommand{
          workspaceId
        elementId
        elementType
        ingestionState      
      }
      
    }
  }

The ingestionState field displays the status of each upload as it changes over time. When the status is complete_success it means that the upload has been completed successfully and you can interact with the object. Before this status, you cannot access or try to modify the still uploading element (you get an error from the API execution).

Example of the events reported by the viewUploadAssetIngestionStateSubscription above for the upload of an image (the values of workspaceId and elementId are sample values to show they belong to the same object being uploaded):

{
  data: {
    item: {
      workspaceId: '9D5UnzlRcvk8ujOgCK-J',
      elementId: '615e12bd90f5ca11cf8d951f',
      elementType: 'Image',
      ingestionState: 'transferring'
    }
  }
}
Observer.next
{ data: { item: {} } }
Observer.next
{
  data: {
    item: {
      workspaceId: '9D5UnzlRcvk8ujOgCK-J',
      elementId: '615e12bd90f5ca11cf8d951f',
      elementType: 'Image',
      ingestionState: 'processing'
    }
  }
}
Observer.next
{
  data: {
    item: {
      workspaceId: '9D5UnzlRcvk8ujOgCK-J',
      elementId: '615e12bd90f5ca11cf8d951f',
      elementType: 'Image',
      ingestionState: 'complete_success'
    }
  }
}

Example of use of subscriptions: use the cursor to reconnect to a subscription

GraphQL subscriptions are long-lasting operations that can deliver changes in real-time. GraphQL commands subscription provides you with live updates that happen in a workspace. For example:

subscription($workspaceId: String!) {
  commands(workspaceId: $workspaceId) {
    ... on CreateElementCommand {
      element {
        id
        type: __typename
      }
    }
  }
}

GraphQL subscriptions deliver changes as long as the underlying connection is active. If the connection is terminated, the client must reconnect by re-sending the subscription operation. In this case, if changes happened to elements or to the workspace in between disconnect and reconnect events, the client will not receive these changes. To avoid loss of data your client can use cursors. GraphQL subscription cursors are identifiers that exist on every command, similar to the concept of an event unique ID. Then, it is a good practice to include the cursor field in the subscription.
Your client can subscribe and receive GraphQL subscription commands with cursors. See the following example:

subscription($workspaceId: String!) {
  commands(workspaceId: $workspaceId) {
    cursor
    ... on CreateElementCommand {
      element {
        id
        type: __typename
      }
    }
  }
}

If your subscription was interrupted and you want to get all the events since the last available cursor, then provide the last known cursor value to a subscription like in the example below. This subscription also keeps receiving new events. It is like saying: “create a subscription, listing all the events since lastKnownCursor and all the new ones”.

subscription($workspaceId: String!, $lastKnownCursor: ID!) {
  commands(workspaceId: $workspaceId, cursor: $lastKnownCursor) {
    cursor
    ... on CreateElementCommand {
      element {
        id
        type: __typename
      }
    }
  }
}

Use these variables:

{
    "workspaceId": "<workspaceId>",
    "lastKnownCursor": "<last_known_cursor_value>"

}

The subscription lists all the events since the value of lastKnownCursor, and your client can process them. All the missed events are in the correct sequence.

Where to Next?

Not what you were looking for? Reply below or Search the community and discover more Bluescape.