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
- OpenFin
- Minimum recommended runtime version 20.91.64.3.
- Salesforce
- Org with System Administrator profile, or sign up for a free Salesforce developer org.
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.
-
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.
-
-
Copy your Consumer Key. You will provide this value in your OpenFin app code.
-
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.
-
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 formhttps://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, theexecuteApiRequest
lets you either explicitly set the REST API version to call, or use a value ofvXX.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 toapplication/json
by default. This means you need to set only the value of thedata
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}`);
});
}
Updated 5 months ago