Skip to main content

Salesforce

Overview

The Context Synchronization module (io_context) keeps the Contact record displayed in Salesforce synchronized with the context data of your other interop-enabled apps. When the user opens a Contact record in Salesforce, the module publishes the Contact and its linked Account. When another app publishes a Contact, Salesforce navigates to the matching record.

The module can publish and read the data either in a Salesforce-native record format, or as an FDC3 context. It works with the Workspace context when Salesforce is running in a Workspace, and with the io.Connect Channels otherwise.

The module is distributed as the contextSyncUtilityBar Utility Bar component, and as the Fdc3ContextMixin mixin, which you can apply to an existing interop-enabled component.

⚠️ Note that the Context Synchronization module is available since Salesforce Adapter 6.100 and is currently in beta. It supports only the Contact object with its linked Account, and a fixed set of fields - name, email, Account ID and Account name.

Requirements

  • Lightning Web Security must be enabled in your Salesforce organization.

  • The io_context module must be included in your package build. If your installed version is earlier than 6.100, request a new build that includes the module.

  • Salesforce must be defined as an io.Connect app. To use the io.Connect Channels instead of the Workspace context, the app definition must also assign a Channel to the app.

Configuration

The module is configured via the "sfContext" object, which the module looks up in the following order:

  1. The "meta" object of the io.Connect Channel definition.

  2. The "customProperties" object of the Salesforce app definition.

The following example demonstrates configuring the module in a Channel definition:

{
    "name": "Red",
    "meta": {
        "color": "red",
        "sfContext": {}
    }
}

The following example demonstrates configuring the module in a Salesforce app definition:

{
    "customProperties": {
        "sfContext": {}
    }
}

The "sfContext" object has the following properties:

Property Type Description
"account" object Paths at which the account record and its fields will be written in the context data. Ignored when "fdc3" is set to true.
"contact" object Paths at which the contact record and its fields will be written in the context data. Ignored when "fdc3" is set to true.
"fdc3" boolean If true, the module will publish and read an FDC3 context instead of a Salesforce-native record. Defaults to false.

An empty "sfContext" object activates the module with the default paths described below.

⚠️ Note that if the module is running in a Workspace, it activates automatically with the default paths even when no "sfContext" configuration is found. Provide a configuration only if you want to change the paths or switch to FDC3 mode.

⚠️ Note that if the module isn't running in a Workspace, it stays idle until an "sfContext" configuration is found, and retries every time the user switches the Channel of the Salesforce window.

Record Paths

The "contact" and the "account" objects control where the respective records and their fields are written in the context data. The "object" property specifies an absolute path in the context data at which the entire record is replaced on every publish. All other properties specify paths relative to that record.

The "contact" object has the following properties:

Property Type Description
"email" string | null Path to the Contact email. Set to null to exclude the field. Defaults to "id.email".
"id" string | null Path to the Salesforce ID of the Contact. This is also the path from which the module reads the ID of an incoming Contact. Set to null to exclude the field. Defaults to "id.salesforce".
"name" string | null Path to the Contact name. Set to null to exclude the field. Defaults to "name".
"object" string Path to the contact record itself. Defaults to "contact".

The "account" object has the following properties:

Property Type Description
"id" string | null Path to the Salesforce ID of the Account. Set to null to exclude the field. Defaults to "id.salesforce".
"name" string | null Path to the Account name. Set to null to exclude the field. Defaults to "name".
"object" string Path to the account record itself. Defaults to "contact.account".

⚠️ Note that the "object" properties must be non-empty strings. Omit the property to use the default value - setting it to null or to an empty string is a configuration error and will prevent the module from initializing.

With the default paths, the account record is nested in the contact record, so both records are published at a single location:

{
    "contact": {
        "name": "John Smith",
        "id": {
            "email": "john.smith@example.com",
            "salesforce": "003xxxxxxxxxxxxxxx"
        },
        "account": {
            "name": "Acme Corp",
            "id": {
                "salesforce": "001xxxxxxxxxxxxxxx"
            }
        }
    }
}

If you point "account" to a path outside the contact record, the two records are published separately and the contact record won't contain any account data. This enables one app to consume only the Contact details, and another one to consume only the Account details.

The following example demonstrates publishing the two records at separate locations and with custom field names:

{
    "meta": {
        "sfContext": {
            "contact": {
                "object": "io.contact",
                "id": "sfId",
                "email": "mail",
                "name": "fullName"
            },
            "account": {
                "object": "io.account",
                "id": "sfId",
                "name": "title"
            }
        }
    }
}

The configuration above produces the following context data:

{
    "io": {
        "contact": {
            "sfId": "003xxxxxxxxxxxxxxx",
            "mail": "john.smith@example.com",
            "fullName": "John Smith"
        },
        "account": {
            "sfId": "001xxxxxxxxxxxxxxx",
            "title": "Acme Corp"
        }
    }
}

⚠️ Note that a publish replaces the entire record, so a Contact without a linked Account is published with an account record set to null. This clears any account data left from a previously selected Contact.

FDC3 Mode

Set the "fdc3" property to true to publish and read an FDC3 context instead of a Salesforce-native record:

{
    "name": "Red",
    "meta": {
        "color": "red",
        "sfContext": {
            "fdc3": true
        }
    }
}

The module publishes a single fdc3.contact context with the organization nested in it:

{
    "type": "fdc3.contact",
    "name": "John Smith",
    "id": {
        "email": "john.smith@example.com",
        "salesforce": "003xxxxxxxxxxxxxxx"
    },
    "organization": {
        "type": "fdc3.organization",
        "name": "Acme Corp",
        "id": {
            "salesforce": "001xxxxxxxxxxxxxxx"
        }
    }
}

In a Workspace, the context is written at the fdc3.contact path of the Workspace context. On a Channel, it's published via the broadcast() method of the FDC3 API.

⚠️ Note that the "contact" and "account" paths are ignored in FDC3 mode, and the organization is always nested in the contact context.

Inbound Records

When another app publishes a Contact, the module navigates to the Salesforce record with the ID found at the configured ID path.

If the incoming Contact has no Salesforce ID, the module searches your Salesforce database instead:

  • If the Contact has an email, the search matches the email exactly.

  • Otherwise, the search matches the Contact name partially, and also the Account name partially if an account record is present.

If the search returns more than one Contact, the module navigates to the first one. If the search returns no results or fails, the module logs a warning, displays a toast message, and stays on the current page.

Usage

The contextSyncUtilityBar component is the ready-to-use entry point of the module. Attach it to the Utility Bar of your Lightning app as described in the io.Connect Platform > Attaching the Utility Bar Component section.

If your app already uses an interop-enabled Utility Bar component, apply the Fdc3ContextMixin mixin to it instead of adding a second utility item. The implementation of the contextSyncUtilityBar component below is also the reference implementation for this case.

Example XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__UtilityBar</target>
    </targets>
</LightningComponentBundle>

Example HTML structure:

<template>
    <!-- The `slds-is-relative` class positions the connection spinner of the component. -->
    <interopio-workspace-utility-bar
        class="slds-is-relative"
        application-name="my-context-sync-utility-bar"
        factory-config={ioConfig}
    >
        <!-- Required by the mixin to load the FDC3 library at runtime. -->
        <div lwc:dom="manual"></div>
    </interopio-workspace-utility-bar>
</template>

Example implementation:

import { api, track, wire } from "lwc";
import { NavigationMixin, CurrentPageReference } from "lightning/navigation";
import { getRecord } from "lightning/uiRecordApi";
import CONTACT_NAME_FIELD from "@salesforce/schema/Contact.Name";
import CONTACT_EMAIL_FIELD from "@salesforce/schema/Contact.Email";
import CONTACT_ACCOUNT_ID_FIELD from "@salesforce/schema/Contact.AccountId";
import CONTACT_ACCOUNT_NAME_FIELD from "@salesforce/schema/Contact.Account.Name";
import WorkspaceUtilityBar from "interopio/workspaceUtilityBar";
import { Fdc3ContextMixin } from "interopio/ioContextMixins";

const CONTACT_FIELDS = [
    CONTACT_NAME_FIELD,
    CONTACT_EMAIL_FIELD,
    CONTACT_ACCOUNT_ID_FIELD,
    CONTACT_ACCOUNT_NAME_FIELD
];

export default class MyContextSyncUtilityBar extends Fdc3ContextMixin(NavigationMixin(WorkspaceUtilityBar)) {
    // The Workspaces API is required for detecting whether Salesforce is running in a Workspace.
    // The Channels API is required for the Channel mode.
    ioConfig = { workspaces: true, channels: true };

    @track _recordId = null;

    // A Utility Bar component never receives a record ID from the Salesforce platform,
    // but the property keeps the field available for a parent component or a test.
    @api
    get recordId() {
        return this._recordId;
    };
    set recordId(value) {
        this._recordId = value;
    };

    // Tracks the active record. Only Contact pages set the record ID, so navigating away
    // clears it and prevents publishing an outdated Contact on reconnect.
    @wire(CurrentPageReference)
    handlePageRef(pageRef) {
        const isContactPage = pageRef?.type === "standard__recordPage"
            && pageRef?.attributes?.objectApiName === "Contact";

        this._recordId = isContactPage ? pageRef.attributes.recordId : null;
    };

    // The mixin decides whether to publish the record.
    @wire(getRecord, { recordId: "$_recordId", fields: CONTACT_FIELDS })
    handleContact(result) {
        this._applyContactResult(result);
    };

    onError() {
        super.onError();

        console.error("Something went wrong, check the latest error logs.");
    };
};

Troubleshooting

  1. The module doesn't navigate to a record although the Channel context already contains contact data.
  • Make sure you have selected a Channel.

  • Make sure that an "sfContext" object is present either in the "meta" object of the Channel definition, or in the "customProperties" object of the Salesforce app definition. Without it, the module stays idle and never subscribes for Channel updates.

  • Make sure that the configured mode matches the published data. An fdc3.contact context is read only when "fdc3" is set to true, and a Salesforce-native record is read only when it isn't.