Skip to content

Instantly share code, notes, and snippets.

@mikaelmello
Forked from timkinnane/clientcommands.md
Last active June 26, 2018 01:01
Show Gist options
  • Save mikaelmello/62ca0b71d8b056d1c1754b6196fa0bea to your computer and use it in GitHub Desktop.
Save mikaelmello/62ca0b71d8b056d1c1754b6196fa0bea to your computer and use it in GitHub Desktop.
Client Commands

I'd like to introduce a new utility called Client Commands, a solution created to allow the Rocket.Chat server to trigger actions in subscriber clients (bots and possibly other websocket clients). This is handled at the adapter and/or SDK level, not by final users (e.g. normal bot developers).

The problem

Bots subscribe to a message stream and respond to message events, but there's no way for the server to prompt them to do anything other than that.

In order to provide a range of new management features for administrating bot clients, getting data or triggering any non message response action, we need to send data to be interpreted by the client as a command. Such data, identified here by ClientCommands, could not be sent through the normal message stream, because:

  • a) it would need hack workarounds to filter commands from normal message data
  • b) it would be kept forever, bloating message collection storage

The solution

While some features could be added with specific implementations, ClientCommands provides flexibility, making it useful for multiple cases. It keeps code DRY by providng a core utility to developers so they're not re-inventing a range of inconsistent solutions. It should be a fairly obvious architecture to learn as well, accelerating the pace of development for new client management features.

Some management features that can be implemented with the ClientCommands are:

  1. Check bot's aliveness - As commented, it could be a simple endpoint, probably HTTP. But we could send instead a simple 'heartbeat' ClientCommand and the client will send a response indicating that it is alive.
  2. Pause/resume bots - Providing a single interface for an admin to pause the operation of bots can be done by sending a 'pauseMessageStream' command, it will be handled in the SDK that will stop receiving any messages from the server. This can be useful for admins that don't have access to where the bot is being hosted and need to stop it.
  3. Clear cache / reset memory - This is theoretical, but could be something useful for adapters that have cache that might not be working correctly or contain data that needs to be deleted (e.g. user was removed from server, for GDPR compliance), so the an admin can send a command to the adapter, that will then clear its cache and reply indicating success or failure. While this particular management option might not be useful to all, it shows that you can manage anything implemented by the SDK/Adapter, as long as it listens to the specified command.

Useful features that improve UX and can be added with ClientCommands:

  1. Autocomplete bots commands - Since each bot framework behaves differently when asking for the available commands, even if they all use the 'help' message, the response format is different. Each adapter can listen to the 'availableCommands' ClientCommand, and knowing its architecture, get the commands in an array and reply. On server-side, by sending the ClientCommand 'availableCommands', we can store the response in the bot User model with a common syntax for all adapters, making it possible for the server to know which commands the bot can respond to.
  2. Callbacks - An adapter might provide an interface, on top of the ClientCommands, to add callbacks to be called upon conversational context. As approached by Tim here.

The implementation

While a simple publication and collection would be enough to send ClientCommands, there is an overhead when dealing with collections that might become a problem in larger systems.

Therefore, after discussing with Sing Li, meteor-streamer was picked to handle the communication from server to clients, it does not use a collection on server-side so it reduces a lot of overhead.

The package rocketchat-client-commands has a function to send ClientCommands, a method to reply to ClientCommands (called by the client) and a startup file to set up the stream.

Sending a command (in the server)

To send a ClientCommand all you need to do is to call the function via RocketChat.sendClientCommand(user, command [, timeout]), where:

  • user: Object of the target user, containing the _id and username properties
  • command: Object of the command, where it must have at least the key property, a unique string
  • timeout: Optional parameter of the timeout for the client to reply the command, defaults to 5 seconds.
  • It returns a promise that resolves with a reply or rejects with a timeout error

This function adds a listener for a 'client-command-response-<_id of the command>' event in the internal EventEmitter called RocketChat and initiates a timeout.

If the listener is called, the timeout is cleared and the function resolves with the event's first parameter, the ClientCommand's response object.

If the listener is not called within the timeout period, the timeout removes the listener and rejects the promise.

Example - Sending a pauseMessageStream command:

const bot = {
  _id: 'e9e89dqw823d81',
  username: 'bot'
};

RocketChat.sendClientCommand(bot, { key: 'pauseMessageStream' })
  .then((response) => {
    // client replied the command
    console.log(response);
  })
  .catch((err) => {
    // client did not reply within 5 seconds
    console.log(err);
  });

Replying to commands (in the client)

Once the client receives the command and acts according to it, it should call the replyClientCommand method (on the server, via SDK driver) with two parameters, the _id of the command and the response object.

The method will then emit a 'client-command-response-<_id of the command>' event via RocketChat.

@timkinnane
Copy link

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment