# Communication Between Controllers

Because a controller can operate in the Captivate UI and also in a local browser, it’s quite possible for you to have multiple instances of the same controller in two different places both communicating with Captivate. This can cause problems unless the two instances of the controller know about each other and are able to communicate.

In fact, there are many reasons why one controller would want to communicate directly with another controller, so Captivate provides two methods designed to address this specific situation. For example, if you have one controller that has one UI running in Captivate and another UI designed for a remote operator, you can keep both interfaces in sync by sending messages through Captivate.

## Using `messageIn` and `messageOut`

Controllers can pass messages back and forth to other controllers or to other instances of themselves by using the `messageOut` method in combination with the `messageIn` signal.

To use, call the `messageOut` method with three arguments:

* `from` - A string identifying the source of the message (usually the controller name, but may be anything).
* `to` - A string identifying the target of the message (usually the other controller name, but may be anything).
* `data` - A JavaScript object of key, value pairs. This may be an arbitrarily deeply nested object, but ***it must be an object***. Strings, arrays, and other primitives will result in an error.

> **Note:** The "to" field doesn't actually do any message routing in the backend. When using `messageOut`, ALL subscribers to the `messageIn` event emitter will receive the same three arguments and must do their own filtering. Additionally, this means that anything connected to the Captivate API will be able to read any messages sent this way.

**Example:**

```javascript
const my_id = '12345678';
const target_id = '87654321';
const scheduler = ServiceHandler.scheduler;
scheduler.messageIn.connect((from, to, json) => {
  // filter out unwanted messages
  if (to != my_id) return;
  console.log('messageIn received');
  console.log({
    from,
    to,
    data: JSON.parse(json),
  });
});

scheduler.messageOut(
  my_id,
  target_id,
  {
    hello: 'world',
    list: [1, 2, 3],
    nested: { level: { deep: 0 } },
  },
  (e) => console.log((e === `Sent to receiver`) ? 'success!' : 'failed to send');
);
```

## Using Targeted Commands

Instead of `messageIn` and `messageOut`, you can also use `scheduleCommand` to send targeted commands between controllers.

To turn a command message into a targeted command, add a `to` field to your parameters object. It’s also helpful to use a `sender` field as you'll see below.

> **Note:** The `sender` in a targeted command is not related to the `sender` field in our notifications. It's merely a convention our internal controllers use to identify what input sent the command. You could just as well use `from` for this purpose in your controllers.

### Sending

Ordinarily, this command will return a large amount of data about the current project:

```javascript
await ServiceHandler.scheduler.scheduleCommand('getTitleControlInfo', {}, {});
```

However, by adding a `to` field to the parameters Captivate will *ignore the command internally* and just pass the entire payload around to the other inputs (with a few modifications, see below).

If the targeted input can’t be found, Captivate will reply with an error, but whether the other input is found or not, the command message will always be ignored by Captivate and the payload will always get sent to all other controllers listening on the notification stream.

```javascript
const me = 'My Controller: Instance 2';
const target = 'My Controller: Instance 1';
await ServiceHandler.scheduler.scheduleCommand('my-custom-command', { to: target, sender: me }, {});
```

If the specified input was connected to a title in the project, this is the result:

```json
{
  "command": "my-custom-command",
  "reply": "my-custom-command",
  "success": true
}
```

If the input is not connected to any project title, this is the result:

```json
{
  "command": "my-custom-command",
  "reply": "my-custom-command",
  "error": "No such input",
  "success": false
}
```

Regardless of whether the input is found, the command is ignored by Captivate and the following payload will be sent through the notification system:

```json
{
  "command": "my-custom-command",
  "to": "My Controller: Instance 1",
  "sender": "My Controller: Instance 2"
}
```

### Receiving

To receive targeted messages, you must be listening to the notification stream. Subscribing to notifications is described elsewhere, but here's an example again.

**Example:**

Make sure only one instance of the controller is allowed to send API commands:

```javascript
let weAreControlling = true;
const selfId = (selfId = Math.floor(Math.random() * 1e20)
  .toString(36)
  .padStart(8, 'X')
  .substring(0, 8));
const ourName = 'My Controller';

async function requestControl() {
  weAreControlling = true;
  await ServiceHandler.scheduler.scheduleCommand('request_control', { to: ourName, sender: selfId }, {});
}

ServiceHandler.scheduler.scheduleCommand('subscribe', {}, {});
ServiceHandler.scheduler.onNotify.connect((msg) => {
  /* handle notification messages here */
  const data = JSON.parse(msg);
  // ignore messages that we sent
  if (data.sender === selfId) return;

  if (data.to === ourName) {
    switch (data.command) {
      case 'request_control':
        // the other instance is requesting control
        weAreControlling = false;
      default:
        console.log(data);
    }
  }
});
```

### Targeted Command Payload Restrictions

The payload sent to the notification stream is assembled according to the following rules:

* The first argument passed (command name) will populate the `command` field in the JSON payload.
* All key/value pairs from the second argument, what we call `parameters`, will be included as-is in the notification payload.
* The third argument, what we call `variables`, will be included in the notification payload as a child of the `variables` key.

Because of this, controllers can send and receive messages with arbitrary data in the form of key/value pairs by simply adding more fields to the original message payload or by including these values in the `variables` argument. Here is a more thorough example:

```javascript
ServiceHandler.scheduler.scheduleCommand(
  'greetings',
  {
    to: 'API Tour: JSON Command Tester',
    key1: 'hello',
    key2: 'world',
    key3: {
      hello: 'world',
    },
    key4: 1,
  },
  {
    data1: {
      hello: 'world',
    },
    data2: 'happiness',
  },
  console.log
);
```

This will be printed to the console (assuming the target controller exists in the project):

```json
{
  "command": "greetings",
  "reply": "greetings",
  "success": true
}
```

...and this is the JSON payload that will propagate through the notification system:

```json
{
  "command": "greetings",
  "key1": "hello",
  "key2": "world",
  "key3": {
    "hello": "world"
  },
  "key4": 1,
  "to": "API Tour: JSON Command Tester",
  "variables": {
    "data1": {
      "hello": "world"
    },
    "data2": "happiness"
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.newbluefx.com/captivate-api/javascript-api-reference/using-the-servicehandler/communicating-between-controllers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
