Developer

GraphQL subscriptions

GraphQL Subscriptions enable clients to listen to real-time messages whenever important changes happen inside of 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 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 and inspect the subscriptions, and using a script to capture an process the subscription events.

Subscription implementation in GraphQL Playground

You can access the playground via the page. Here is an example of 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 {
      workspaceId
      id
      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. Please interact with an image in the workspace: change its position.
After moving the image, you will see that a new event is reported to the subscription, where the position (x,y) has changed:

Subscription implementation in a script

The implementation in the GraphQL Playground shows us how the data for the subscription will be 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.
Please inspect the examples below to implement a basic parser for the subscription events.

/*
Requires the following libraries and versions:
1) ws, version 7.4.5
2) graphql , version 15.16.1
3) graphql-ws, version 4.5.1

To install them, run:
npm install <library-name>
*/
var ws = require('ws');
var {createClient} = require('graphql-ws');
const JWT_TOKEN = '<SET_TOKEN>';
// Please note the 'wss' protocol
const WS_URL = 'wss://';
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
        id
        data {
          ... on UpdateImage {
            transform {
              x
              y
            }
          }
        }
      }
    }
  }`

subscribe( GRAPHQL_SUBSCRIPTION, {
    workspaceId: WORKSPACE_ID
  });

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

 Update received:
{
  data: {
    commands: {
      workspaceId: '<workspaceId>',
      id: '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

Other very 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 will display 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 will 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 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 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 which 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 will keep receiving the 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 will list all the events since the value of lastKnownCursor, and you client can process them: all the missed events and in the correct sequence.


If you have any questions or comments, please contact us at Bluescape support.