# Lightning Message Service

Source: https://docs.interop.io/adapters/salesforce/usage/lightning-message-service/index.html

## Overview

The Lightning Message Service (LMS) bridge enables interoperability for components that can't connect to the io.Connect framework directly. A single component owns the connection and relays Interop method invocations to and from the other components on the page over Lightning Message Service.

The component that owns the connection is called a broadcaster, and every component that uses it is called a consumer. The Salesforce Adapter provides the broadcaster, and you turn your own components into consumers by applying the `InteropConsumerMixin` mixin.

The bridge is used in the following cases:

- When [using Salesforce in a web browser](https://docs.interop.io/adapters/salesforce/usage/web-browser/index.md). In this mode the io.Connect Utility Bar component is the broadcaster, and the `webBrowserBaseComponent` component is a consumer. This is the only available option, as a Salesforce app is allowed a single connection to the io.Connect framework.

- When [using Salesforce within the io.Connect platform](https://docs.interop.io/adapters/salesforce/usage/io-connect-platform/index.md) in an organization that uses Lightning Locker instead of Lightning Web Security. The platform components require Lightning Web Security, so the `lmsBroadcaster` component is used as a broadcaster instead.

> ⚠️ *Note that Lightning Locker prevents a component in one namespace from reaching a component in another namespace via DOM events or shared objects. Lightning Message Service is one of the few mechanisms that cross namespace boundaries, which is why it's used as a bridge.*

## Requirements

- The broadcaster must be attached as a utility item to the Lightning app in which your components are used. For the App Builder steps, see the [Web Browser > Attaching the Utility Bar Component](https://docs.interop.io/adapters/salesforce/usage/web-browser/index.md#configuration-attaching_the_utility_bar_component) section.

- The utility item of the broadcaster must be configured to start automatically. Otherwise, the broadcaster will initialize only after the user clicks the utility item, and until then your components will remain disconnected.

- The page holding your components and the utility item of the broadcaster must belong to the same Lightning app. Lightning Message Service subscriptions are scoped per app, and messages between different apps are dropped silently.

## Limitations

The bridge doesn't support streaming Interop methods, as each inbound invocation is answered with exactly one response. A method that must send multiple results over time isn't reachable through the bridge.

## Message Channels

The bridge uses the following message channels, which are distributed with the Salesforce Adapter package and are listed under `Setup > Custom Code > Message Channels` in your organization:

| Message Channel | Direction | Description |
|-----------------|-----------|-------------|
| `ConnectionStatusUpdate__c` | Broadcaster → consumer | Current connection status. |
| `ConnectionStatusUpdateRequest__c` | Consumer → broadcaster | Request for the current connection status. |
| `PlatformMessage__c` | Broadcaster → consumer | Invocation of an Interop method registered by the consumer. |
| `PlatformMessageCallback__c` | Consumer → broadcaster | Result from an Interop method invocation. |
| `PlatformMessageRegister__c` | Consumer → broadcaster | Request for registering an Interop method. |
| `PlatformMessageUnregister__c` | Consumer → broadcaster | Request for unregistering an Interop method. |
| `SalesforceMessage__c` | Consumer → broadcaster | Invocation of an Interop method registered by another interop-enabled app. |

An inbound Interop method invocation passes through the bridge as follows:

1. An interop-enabled app invokes an Interop method registered by your component.

2. The broadcaster creates an invocation ID and publishes the method name, the invocation arguments, and the invocation ID on the `PlatformMessage__c` channel.

3. Your component handles the invocation in its `handlePlatformMessage()` hook and publishes the result and the invocation ID on the `PlatformMessageCallback__c` channel.

4. The broadcaster resolves or rejects the invocation `Promise` of the calling app with the result.

## Component API

To interop-enable your component, apply the `InteropConsumerMixin` mixin to its base class:

```javascript
export default class MyConsumer extends InteropConsumerMixin(LightningElement) {};
```

The mixin provides the following methods:

| Method | Accepts | Description |
|--------|---------|-------------|
| `executePlatformMessageCallback()` | `(string, boolean, object)` | Sends the result from an Interop method invocation back to the broadcaster, which relays it to the calling app. Accepts three required arguments - the ID of the invocation (which can be extracted from the message details), a Boolean value denoting whether the invocation `Promise` should be resolved (`true`) or rejected (`false`), and the actual result from the method invocation. |
| `registerMethod()` | `(string)` | Registers an Interop method. Invocations of the method will be delivered to the `handlePlatformMessage()` hook. Accepts as a required argument the name of the Interop method to register. |
| `requestConnectionStatus()` | `-` | Requests the current connection status from the broadcaster. The status will be delivered to the `handleConnectionStatusUpdate()` hook. |
| `triggerOutbound()` | `(object)` | Invokes an Interop method registered by other interop-enabled apps. Accepts as a required argument an object with `method` and `payload` properties specifying the name of the Interop method to invoke and arguments for the invocation. |
| `unregisterMethod()` | `(string)` | Unregisters an Interop method. Accepts as a required argument the name of the Interop method to unregister. |

The mixin provides the following hooks that you can override in your component:

| Hook | Accepts | Description |
|------|---------|-------------|
| `handleConnectionStatusUpdate()` | `(object)` | Invoked when the broadcaster reports its connection status. Receives an object with an `isConnected` property. |
| `handlePlatformMessage()` | `(object)` | Invoked when the broadcaster relays an invocation of an Interop method registered by your component. Receives an object with `method`, `instance`, `payload`, and `callbackID` properties holding the name of the Interop method, information about the calling app, the invocation arguments, and the ID of the invocation. |

> ⚠️ *Note that you must call the respective method of the mixin when overriding a hook, or when overriding `connectedCallback()`, `renderedCallback()`, or `disconnectedCallback()` in your component, as the mixin uses them to set up and tear down its message channel subscriptions and to request the initial connection status.*

> ⚠️ *Note that you must unregister every Interop method your component has registered before it's destroyed, otherwise the registration will remain active in the io.Connect framework.*

## Example Implementation

The following example demonstrates how to interop-enable a LWC. The component registers an Interop method that can be invoked by other interop-enabled apps, and also invokes an Interop method already registered by other interop-enabled apps:

```javascript
import { LightningElement } from "lwc";
import { InteropConsumerMixin } from "c/lmsMixins";

const TEST_INBOUND_METHOD = "My.Test.Inbound.Method";
const TEST_OUTBOUND_METHOD = "My.Test.Outbound.Method";

export default class MyInteropEnabledComponent extends InteropConsumerMixin(LightningElement) {
    // If you don't need to customize this hook, you can skip its declaration.
    connectedCallback() {
        super.connectedCallback();
    };

    disconnectedCallback() {
        // Always unregister your Interop methods before the component is destroyed.
        this.unregisterMethod(TEST_INBOUND_METHOD);

        super.disconnectedCallback();
    };

    // Invoked when the broadcaster reports its connection status.
    handleConnectionStatusUpdate(message) {
        super.handleConnectionStatusUpdate(message);

        if (message.isConnected) {
            // Register an Interop method that other interop-enabled apps can invoke.
            this.registerMethod(TEST_INBOUND_METHOD);
            // Invoke an outbound Interop method.
            this.triggerOutbound({ method: TEST_OUTBOUND_METHOD, payload: { value: "value" } });
        };
    };

    // Invoked when the broadcaster relays an inbound Interop method invocation.
    handlePlatformMessage(message) {
        super.handlePlatformMessage(message);

        const { method, payload, callbackID } = message;

        if (method !== TEST_INBOUND_METHOD) {
            return;
        };

        // Send the result to the broadcaster, which will relay it to the calling app.
        // The invocation `Promise` of the calling app will hang if you skip this call.
        this.executePlatformMessageCallback(callbackID, true, { "OK": true });

        console.log(`Inbound data received: ${payload.value}.`);
    };
};
```

A Quick Action is a good fit for the bridge, because it's created and destroyed every time the user opens and closes it. Use it for outbound invocations only - register your inbound Interop methods from a component with a longer lifetime, such as the Utility Bar.

The following example demonstrates the XML configuration of a Quick Action:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>My Interop-Enabled Quick Action</masterLabel>
    <targets>
        <target>lightning__RecordAction</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordAction">
            <actionType>Action</actionType>
            <objects>
                <object>Account</object>
            </objects>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>
```

The following example demonstrates the implementation of a Quick Action:

```javascript
import { LightningElement, api } from "lwc";
import { InteropConsumerMixin } from "c/lmsMixins";

const TEST_OUTBOUND_METHOD = "My.Test.Outbound.Method";

export default class MyInteropEnabledQuickAction extends InteropConsumerMixin(LightningElement) {
    // Populated by the Salesforce platform.
    @api recordId;

    // Invoked by the Salesforce platform when the user clicks the Quick Action button.
    @api invoke() {
        this.triggerOutbound({ method: TEST_OUTBOUND_METHOD, payload: { recordId: this.recordId } });
    };
};
```

## Importing the Mixin

If your component is in the same package as the Salesforce Adapter, import the mixin directly:

```javascript
import { InteropConsumerMixin } from "c/lmsMixins";
```

If your component is in a different package, the managed package boundary prevents importing the mixin. Use instead a local copy of the mixin, which is distributed in an additional package provided only to clients and for proof of concept projects, and place it in your component folder:

```cmd
myComponent/
├── myComponent.js
├── myComponent.html
├── myComponent.js-meta.xml
└── lmsMixin/
    ├── lmsMixin.js
    └── consumers/
        └── interop.js
```

Import the mixin from the local copy:

```javascript
import { InteropConsumerMixin } from "./lmsMixin/lmsMixin.js";
```

The message channel imports in `consumers/interop.js` must use the namespace prefix of your Salesforce Adapter installation:

| Installed Namespace | Message Channel Import |
|---------------------|------------------------|
| `interopio` | `@salesforce/messageChannel/interopio__PlatformMessage__c` |
| `Tick42` | `@salesforce/messageChannel/Tick42__PlatformMessage__c` |
| No namespace | `@salesforce/messageChannel/PlatformMessage__c` |

## Troubleshooting

1. Your component never becomes connected although the utility item of the broadcaster is present in the app.

- Make sure that the page holding your component and the utility item of the broadcaster belong to the same Lightning app.

- Make sure that the utility item of the broadcaster is configured to start automatically.

2. The invocation `Promise` of the calling app never resolves or rejects.

- Make sure that every invocation handled in `handlePlatformMessage()` calls `executePlatformMessageCallback()`. There is no timeout on the side of the calling app.

3. An Interop method remains registered after your component is removed from the page.

- Make sure that your component calls `unregisterMethod()` for every Interop method it has registered before `disconnectedCallback()` completes.
