Salesforce
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. In this mode the io.Connect Utility Bar component is the broadcaster, and the
webBrowserBaseComponentcomponent 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 in an organization that uses Lightning Locker instead of Lightning Web Security. The platform components require Lightning Web Security, so the
lmsBroadcastercomponent 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 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:
An interop-enabled app invokes an Interop method registered by your component.
The broadcaster creates an invocation ID and publishes the method name, the invocation arguments, and the invocation ID on the
PlatformMessage__cchannel.Your component handles the invocation in its
handlePlatformMessage()hook and publishes the result and the invocation ID on thePlatformMessageCallback__cchannel.The broadcaster resolves or rejects the invocation
Promiseof the calling app with the result.
Component API
To interop-enable your component, apply the InteropConsumerMixin mixin to its base class:
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(), ordisconnectedCallback()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:
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 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:
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:
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:
myComponent/
├── myComponent.js
├── myComponent.html
├── myComponent.js-meta.xml
└── lmsMixin/
├── lmsMixin.js
└── consumers/
└── interop.jsImport the mixin from the local copy:
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
- 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.
- The invocation
Promiseof the calling app never resolves or rejects.
- Make sure that every invocation handled in
handlePlatformMessage()callsexecutePlatformMessageCallback(). There is no timeout on the side of the calling app.
- 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 beforedisconnectedCallback()completes.