How to utilize HistoryCommand.clientId

When I subscribe to commands and listen to the events, and when I react to events and run mutation, I get command event of the mutation as well

I am worry about infinite loop and also some other complexities. I found that HistoryCommand.clientId is a consistent string when user do the operation but it’s a random string when my client do the mutation

My question is: How can I detect that a command is done by user or my programmed client? If it’s possible it makes my code more simplified

The pseudo code shows what I am looking for

on('UpdateElementCommand',()=>{
    if(done_by_user){
          move_element() // Done by client
     }else{
         ignore_this_event()
     }
})

Hi @Amerehei,

When using commands subscription, it will return events for all users, including the API user. However, you can use actorId to know what userId has performed the action to identify if the user that performed the action was your API user, or a normal user.

The bearer token used for mutation API has it’s own userId which is the same as the actorId, and a user also has it’s own unique actorId.

For example, with this commands subscription you can get the actorId:

subscription subscribeToCommand($workspaceId: String!) {
  commands(workspaceId: $workspaceId) {
    ... on UpdateElementCommand {
      ##actorId is the user that performed the action:
      actorId
    }
  
  }
}

with the response:

{
  "data": {
    "commands": {
      "actorId": "BC0i0wzZ5Caph7i871Kb"
    }
  }
}

You can get the userId for your bearer token by looking up the profile tied to the bearer token with the following profile query:

query getMyProfile{
   me {
        profile{
            id
        }
    }
}

with the response:

{
    "data": {
        "me": {
            "profile": {
                "id": "BC0i0wzZ5Caph7i871Ka"
            }
        }
    }
}

You can store the API userId and then any other actorId returned from the subscription is the action performed by a normal user.

Let me know if this solves the problem for you, or if you have any additional questions.

Unfortunately the actorId are the same in both situation. We subscribe to the events in the frontend and for security reasons we use the user’s token for GraphQL

Right now it’s not a big deal to don’t detect user/client and there are some alternative algorithm for me to continue.

I am wondering why the clientId is a random id for every single mutation

Hi @Amerehei,

Thanks for the clarification - that makes sense. I’ll need to confirm the best way to identify API vs webC use if they are both using the same user, and I’ll need get back to you shortly.

1 Like

@kkoechley Recently I have added some very complex codes and without knowing the origin of mutation (User or Client) we have some unexpected behaviour in our application.

I hope we can find a way to detect it

hi @Amerehei,

I’m still working this out to see if there is a better way to identify if your app or the user (in the browser) is initiating the subscription event, but we don’t have a direct way to identify which is which.

The clientId is tied to session, so it should be consistent from the web client, but API it is new for each mutation due to your app not having access to the bluescape session like a user would in the web client.

If you are performing an action programmatically, perhaps you could set a trait on the element to know that it was done via API, and thus ignore it when getting the update element command? I’ll have to play around with this a bit more, and I’ve raised this use case as an area for improvement, but for now you’ll have to use a workaround.

@kkoechley I thought about the trait but unfortunately it’s not the solution for two reason

  1. If a bot adds a traits to an element, it’s persisted to the element so if a user do something on that element the trait value doesn’t change
  2. the HistoryCommand interface and all other implemented classes such as UpdateElementCommand don’t expose trait

Is it safe to hard code all BS client IDs and have this logic? because when our bot do it, the clientId is a random string but when a user do it, the clientId is fixed value and it’s probably the clientId of BS Web App

const isUser = bsClientIds.include(HistoryCommand.clientId)

if yes, Can you provide me all of BS clientIds?

Hi @Amerehei,

I don’t think the clientId is going to give you reliable results, as it is tied to browser session for each user and/or browser.

If you are using traits from bot user, you would need to do something creative to store a unique id when making the mutation call, and then compare the value from the mutation to the subscription. The traits would persist, so they would be returned for user action as well, which is why you would need to store the traits id for mutation and then expect the subscription almost immediately after making the mutation call.

I know this isn’t ideal - but hopefully there is a creative workaround solution to help identify the difference between bot/user. I’ll have to keep playing with it and will let you know if i get any other ideas.

@kkoechley How is it possible to get the trait of mutation in the UpdateElementCommand? I cannot select to trait while listening on UpdateElementCommand

Hi @Amerehei,

to get the updated traits from the subscription you would want to use the UpdateTraitsCommand in addition to the UpdateElementCommand.

For example

You can set traits on the update mutation and update the x,y:

#update shape properties
mutation myUpdateShape($workspaceId:String!, $x:Float!, $y:Float!, $shapeId:String!){

    myShapetraits: updateTraits(workspaceId: $workspaceId, dryRun:false, id: $shapeId, input: {context: "http://schema.org/", content: {testTraits: 1}})

    myShape:updateShape(workspaceId:$workspaceId, id:$shapeId, input:{
        transform:{x:$x y:$y}
   })
   {id}
}

And then use the following subscription to listen listen for both shape x,y and update traits:

subscription subscribeToCommand($workspaceId: String!) {
  commands(workspaceId: $workspaceId) {
    __typename
    
    ... on UpdateElementCommand {
      ##actorId is the user that performed the action:
      updateActorId:actorId
      clientId
      actorType
      elementId
      data{
        ... on UpdateShape{
          transform{
            x y
          }
      	}
      }
    }
    
    ... on UpdateTraitsCommand{
    	updateTraitsActorId:actorId
      clientId
      actorType
      elementId
      traits
    } 
  }
}

with both being returned separately:

{
  "data": {
    "commands": {
      "__typename": "UpdateTraitsCommand",
      "updateTraitsActorId": "BC0i0wzZ5Caph7i871Ka",
      "clientId": "65822ba15203ed80b8e5d8d7",
      "actorType": "USER",
      "elementId": "658218535203edf34ee5c1c2",
      "traits": {
        "http://schema.org/testTraits": 1
      }
    }
  }
}

and

{
  "data": {
    "commands": {
      "__typename": "UpdateElementCommand",
      "updateActorId": "BC0i0wzZ5Caph7i871Ka",
      "clientId": "65822ba15203ed89bfe5d8d9",
      "actorType": "USER",
      "elementId": "658218535203edf34ee5c1c2",
      "data": {
        "transform": {
          "x": 100,
          "y": 100
        }
      }
    }
  }
}

Ah! Too complex!

As I get UpdateElementCommand and UpdateTraitsCommand in separate events, I need to connect them in my app

In the mutation myUpdateShape you have updateTraits and then updateShape

My question is, Can Bluescape guarantee that I first get UpdateTraitsCommand and then UpdateElementCommand because of the order of mutations? Is it possible that Bluescape run updateShape sooner than updateTraits and I get the events in reverse order?

Hi @Amerehei,

Sorry for the complex workaround. I’ve raised your use case to our product and engineering team and will let you know of any changes to make this simpler.

With regard to order of subscription events, yes, the order of the subscription commands are tied to the order in your mutations.

If your mutation has updateTraits first and then updateShape, your subscription commands will be returned in that order with UpdateTraitsCommand followed by UpdateElementCommand.

Thank you @kkoechley for your great following up

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.