Salesforce integration with OpenFin Container

Overview

OpenFin provides an API to help simplify connecting your OpenFin app with Salesforce. The API integrates with the Salesforce REST API and OAuth 2.0 to provide secure interoperability between an app running in OpenFin Container and any Salesforce org.

This page shows you how to:

  • Configure authorization for your Salesforce org
  • Connect your OpenFin app to your Salesforce org
  • Make requests to the Salesforce REST API

Prerequisites

Install

Add the @openfin/salesforce NPM package:

With npm:

npm i @openfin/salesforce

With yarn:

yarn add @openfin/salesforce

Configure Salesforce authorization

Create a new Salesforce Connected App definition that registers your app with your Salesforce org and grants it the required permissions.

See also OAuth and Salesforce authorization for OpenFin Container for more detailed information.

  1. In the Salesforce UI, create a new Connected App and specify the following OAuth settings:

    • Callback URL: https://login.salesforce.com/services/oauth2/success

    • OAuth scopes:

      • Manage user data via APIs
      • Perform requests at any time

    These are minimum scopes. Your app may require additional scopes.

    • Make sure that secrets are not required for any available flows.
  2. Copy your Consumer Key. You will provide this value in your OpenFin app code.

  3. Edit the OAuth Policies for your Connected App to set the refresh token to expire after 1 day. OpenFin recommends this interval, but you can set it to whatever your situation requires.

  4. Add the origin URL of your OpenFin app to the CORS allowlist and in Cross-Origin Resource Sharing (CORS) Policy Settings, ensure that Enable CORS for OAuth endpoints is selected.

Connect your OpenFin app to your Salesforce org

📘

Starter Project

Take a look at the starter project for the Salesforce integration for a working code example.

Call the OpenFin connect function to begin the Salesforce authorization flow needed for the rest of your integration. This function opens a window that prompts the user to log in to Salesforce and authorize the OpenFin app to make REST API requests:

import { connect } from '@openfin/salesforce';

const salesforceOrgBaseUri = 'SALESFORCE_DOMAIN_NAME';
const consumerKey = 'CONNECTED_APP_CONSUMER_KEY';

const salesforce = await connect(salesforceOrgUrl, consumerKey);

Where:

  • salesforceOrgBaseUri = the base URI of the connected Salesforce org, in the form https://MY_DOMAIN_NAME.my.salesforce.com
  • consumerKey = the consumer key of your Connected App, required for authorization

After authorization succeeds, a SalesforceConnection object is returned. You work with this object to make requests to the Salesforce REST API.

Retrieve an existing connection

If the app is closed, or if the reference to the SalesforceConnection object is otherwise lost, an existing connection that's still valid can be resumed. This can provide a good user experience when the app is reopened.

The getConnection function returns a promise that resolves with either a SalesforceConnection object that represents the existing connection, or null if the connection was not found or is no longer valid (that is, if both access and refresh tokens have expired). To retrieve the connection, the values of the parameters must be the same as the values that were passed to create the connection.

The connect function calls getConnection internally before starting the authorization flow, so that any existing connection that is still valid can be re-used. The getConnection function is preferred over connect because it does not start the authorization process. This lets you check for an existing connection when OpenFin starts, without interrupting the user with an unnecessary authorization flow. The connect function automatically starts the authorization process if no valid connection is found, which can provide a poor user experience.

An existing connection can be retrieved by calling the getConnection function:

import { getConnection } from '@openfin/salesforce';

const salesforceOrgBaseUri = 'SALESFORCE_DOMAIN_NAME';
const consumerKey = 'CONNECTED_APP_CONSUMER_KEY';

const salesforce = await getConnection(salesforceOrgBaseUri, consumerKey);

Disconnect from Salesforce

Make the following call on the SalesforceConnection object:

await salesforce.disconnect();

The authorization tokens are immediately expired and removed from local storage.

Make requests to Salesforce REST API endpoints

Requests to the Salesforce REST API take the form of the following call on the SalesforceConnection object:

const response = await salesforce.executeApiRequest<{ version: string }[]>('/services/data/');
const versions = response.data?.map((x) => x.version);
console.log(`Supported Salesforce versions: ${versions?.join(', ')}`);

Different Salesforce REST API endpoints might require additional OAuth scopes for your Connected App.

The executeApiRequest function returns a SalesforceRestApiResponse object if the request succeeds -- that is, if the returned HTTP status code is 2xx. If the request fails, a RestApiError object is returned. This function accepts an optional type parameter that specifies the type for the data property of the response object.

The Salesforce /services/data endpoint must also include the API version that is requested. However, the executeApiRequest lets you either explicitly set the REST API version to call, or use a value of vXX.X to default to the API version set by the API.

You can make other requests by specifying the HTTP method, data payload and headers:

Create a new contact in Salesforce

const createContactResponse = await salesforce.executeApiRequest(
  '/services/data/vXX.X/sobjects/Contact',
  'POST',
  {
    FirstName: 'Joe',
    LastName: 'Bloggs',
  }
);
if (createContactResponse.data) {
  const { id: newContactId } = createContactResponse.data;
  console.log(`Created contact with id ${id}`);
}

For POST or PATCH requests, the Content-Type header is set to application/json by default. This means you need to set only the value of the data parameter as an object that contains the payload data.

Add response types

To take advantage of TypeScript’s strong typing, the OpenFin Salesforce API includes response types for some of the more common endpoints. The SalesforceRestApiSObject type lets you construct your own object types that are suitable for your Salesforce org, and along with the SalesforceRestApiSObjectCreateResponse type you can add strong typing to the previous example:

// Define a contact type that's part of your Salesforce org
type SalesforceContact = SalesforceRestApiSObject<{ Email: string; FirstName: string; LastName: string; }>;

// Create a new contact
const newContactData: Partial<SalesforceContact> = {
  FirstName: 'Joe',
  LastName: 'Bloggs',
};
const createContactResponse = await salesforce.executeApiRequest<SalesforceRestApiSObjectCreateResponse>(
  '/services/data/vXX.X/sobjects/Contact',
  'POST',
  newContactData
);
if (createContactResponse.data) {
  const { id: newContactId } = createContactResponse.data;
  console.log(`Created contact with id ${id}`);
}

You can then update or delete the contact:

// Update the contact using xml data
const updatedContactData = '<Contact><Email>[email protected]</Email></Contact>';
await salesforce.executeApiRequest(
  `/services/data/vXX.X/sobjects/Contact/${newContactId}`,
  'PATCH',
  updatedContactData,
  {
    'Content-Type': 'application/xml',
  }
);

// Delete the new contact
await salesforce.executeApiRequest(`/services/data/vXX.X/sobjects/Contact/${newContactId}`, 'DELETE');

More TypeScript examples

When performing searches, use SalesforceRestApiSearchResponse when specifying the response type. Since searches typically return a number of different object types, utilize union types and type discrimination to process the results in a strongly typed fashion:

// This example requires additional imports
import { SalesforceRestApiSearchResponse, SalesforceRestApiSObject } from '@openfin/salesforce';

// Define object types for my org
type SalesforceContact = SalesforceRestApiSObject<{ Email: string; Name: string }>;
type SalesforceAccount = SalesforceRestApiSObject<{ Name: string; Phone: string; Type: string }>;

// Execute search request specifying a union type for the response data
const response = await salesforce.executeApiRequest<
  SalesforceRestApiSearchResponse<SalesforceContact | SalesforceAccount>
>('/services/data/vXX.X/parameterizedSearch', 'POST', {
  q: 'Dickenson plc',
  sobjects: [
    { name: 'Contact', fields: ['email', 'name'] },
    { name: 'Account', fields: ['name', 'phone', 'type'] },
  ],
});

// Process search results using type discrimination
if (response.data) {
  response.data.searchRecords.forEach((record) => {
    if ('Email' in record) {
      const { Email, Name } = record;
      console.log(`Contact found: ${Name}; ${Email}`);
    } else if ('Type' in record) {
      const { Name, Phone, Type } = record;
      console.log(`Account found: ${Name}; ${Type}; ${Phone}`);
    }
  });
}
// This example requires additional imports
import { SalesforceRestApiSObjectBasicInfoResponse, SalesforceRestApiQueryResponse, SalesforceRestApiSObject } from '@openfin/salesforce';

// Define object types for your org
type SalesforceContact = SalesforceRestApiSObject<{ Email: string; Name: string }>;

// Retrieve contact record using the id
const contactId = 'a004L000004RVjMQAW';
const retrieveRecordResponse = await salesforce.executeApiRequest<SalesforceRestApiSObject<SalesforceContact>>(
  `/services/data/vXX.X/sobjects/Contact/${contactId}`
);
if (retrieveRecordResponse.data) {
  const { Email, Name } = retrieveRecordResponse.data;
  console.log(`Contact retrieved: ${Name}; ${Email}`);
}

// Search for contact by name
const query = `SELECT+Id+FROM+Contact+WHERE+Name='Joe Bloggs'`;
const queryResponse = await salesforce.executeApiRequest<SalesforceRestApiQueryResponse<SalesforceContact>>(
  `/services/data/vXX.X/query/?q=${query}`
);
if (queryResponse.data) {
  queryResponse.data.records.forEach((record) => {
    console.log(`Contact found: ${record.Id}`);
  });
}

// Get recently viewed contacts
const recentContactsResponse = await salesforce.executeApiRequest<SalesforceRestApiSObjectBasicInfoResponse<SalesforceContact>>(
  `/services/data/vXX.X/sobjects/Contact`
);
if (recentContactsResponse.data) {
  const { recentItems } = recentContactsResponse.data;
  recentItems.forEach((record) => {
    const { Email, Name } = record;
    console.log(`Recent contact found: ${Name}; ${Email}`);
  });
}

Did this page help you?