Prerequisite knowledge
This article assumes knowledge of Salesforce Automation, AJAX transactions and the Adobe DPS Library and Store API.
User level: Intermediate
Required Adobe products (retail)
Additional required other products (third-party/labs/open source)
  • Salesforce


With Adobe DPS release 31, it is now possible to easily authenticate employees and synchronize their usage data within Customer Relationship Management (CRM) systems such as (SFDC).  Combining this with existing HTML/JavaScript extensibility enables powerful integrations of Adobe DPS with corporate or sales enablement applications.  It is now possible to build applications with SFDC authentication, synchronization of SFDC data objects and end-user usage tracking.
New features which are core to this integration are as follows:
  1. Native OAUth2.0 authentication – the standard for Salesforce authentication
  2. Externalization of Adobe Analytics tracking - previously only available within Adobe Analytics or the DPS Analytics Dashboard.
While this article assumes Salesforce, any service requiring OAuth2.0 would be able to leverage these capabilities.
To learn more about OAuth2.0, please refer these articles:
  1. Wikipedia Overview:
  2. SFDC authentication:

Sales Enablement Scenario

From a DPS application, a sales representatives can now authenticate themselves (using OAuth2.0) into their company’s SFDC account; download/synchronize any needed data records (e.g. Contacts, Opportunities, etc.), and record folio usage events back into the same SFDC account. Doing this enables sales representatives and their management to track which folio content (articles, pages and overlays) have been shown to which specific customers or contacts, and to uncover best practices across the entire sales force.
While authentication and data synchronization can only happen when the device has a valid internet connection, the DPS API analytics tracking API does have a mode to record the events while offline. When offline, the data is recorded and is then available for uploading once the device is online.
Two new API objects have been added to the Library and Store SDK API. These are explained below.
This API manages the Authorization, Renewal and Revocation of access rights to the SFDC account. It is a direct device to SFDC server implementation and no 3rd party web server is required.  It uses the “User-Agent” authentication flow outlined in this SFDC document.
This API manages the collection and recording of Folio interaction events.  Generally, this is the same raw data that is reported into Adobe Analytics and the DPS Analytics Dashboard tools. It contains methods to start/stop the recording of data (i.e. needed when offline) as well as methods and events for collecting the data as it is recorded.

SFDC Application Setup

Within your SFDC Setup Dashboard, you need to create a new Connected Application. We will connect this application to your DPS application via the DPS Application’s URLScheme. Likewise, we will also need to connect the DPS Application to the Salesforce app via the ‘Consumer Key’.
The URLScheme is unique to your application and is specified within DPS App Builder when configuring. ‘Consumer Key’ is unique to Salesforce and uniquely identifies your connected application. In the examples below we refer to this as ‘clientId’.
Figure 1: Creating a new Connected Application in the Setup Dashboard
Figure 1: Creating a new Connected Application in the Setup Dashboard
You will also need to define a New Custom Object in Saleforce named ‘FolioEvent’. This object will receive the Folio Interactivity event data.
The following examples assume that you have a list of opportunities (potential or existing customer names) that are associated with an ‘account’. We will be retrieving the list of opportunities for a given account and then storing Folio Interaction events that reference the opportunity. This is only an assumption and you can modify the snippets to your own personal requirements.


This section describes how to authorize, renew and revoke access to an SFDC account. You will need your own SFDC account to set this up.
OAuth Authentication works by asking SFDC to collect and validate the users credentials. The ‘ask’ is by means of a redirect URI and clientId. The clientId identifies which (of the millions) SFDC application the user wants to connect to and the redirect URI specifies where SFDC will send the authenticated user. The actual credentials are collected and validated by SFDC. Your DPS Application will never see them and has no access to them.
The general approach to setting up the OAuth process is to set up callback listeners and start the process via a HTML container such as provided by the DialogService class. The viewer internals (Obj-C code) will handle success/failure and report back to your JavaScript via callback events.
You will populate the HTML container with the following URL:
  • <path_to_your_sfdc>is the path where you would log in to your Salesforce instance. For production applications, this should be, but will be different for sandboxed development applications.
  • <your_client_id> is the encoded Consumer Key provided by SFDC when you created your connected application. Be sure to use encodeURIComponent to properly encode this as a URL parameter.
  • <your_redirect_uri> is the exact same redirect URI you defined when you created your connected application within SFDC. It should contain the URLScheme of your DPS iOS application (e.g. ‘myapp://oauth’).
Note: Use a real and unique URLScheme. Otherwise, every SFDC connected DPS app will be labeled “myapp”.
The following example defines a data object to hold our client specific settings. It then defines two callbacks that will be used when the OAuthRedirectService is started. The callback successCB is called to initiate the login sequence. The callback authCB is called when the authentication process is complete. Once complete, the OAuthRedirectService object will contain a populated oauthData object. The data within this object is used to revoke and renew a client authentication.
A simple error helper function is also called. The details of this implementation are left to your imagination.
var oauthConfig = { authURL : "", redirectURL : "myapp://oauth", clientId : "3MVG9dPGzpc3kWydtN1fyM0sPLLckz2fjkGCr2sxzHpBsiEO0yOKJZIeeKwFJ05zWtE" }; // Initialize the OAuthRedirectService with our client data try { var oauth = { authURL : oauthConfig.authURL, clientId : oauthConfig.clientId, redirectURI : oauthConfig.redirectURL }; adobeDPS.oauthRedirectService.initAuthData( oauth, function() {}, // Success callback function() {}; // Error callback } catch (error) { error(error); } function oasAuthorize() { function successCB() { var loginUrl = oauthConfig.authURL + ‘/services/oauth2/authorize’; loginUrl += “?display=touch&response_type=token&state=validation”; loginUrl += “&client_id=” +encodeURIComponent(oauthConfig.clientId); loginUrl += “&redirect_uri=” +adobeDPS.oauthRedirectService.redirectURI;; } function authCB(response) { adobeDPS.dialogService.close(); if (response.hasOwnProperty(“timeout”)) { console.log(“the authentication request timed out”); } if (response.hasOwnProperty(“error”)) { console.log(“the authentication server returned error”); } else { // the authentication server returned success console.log(“authentication successful”); } } try { adobeDPS.oauthRedirectService.startListening(authCB, successCB, error); } catch (error) { error(error); } }
Renew Authorization Token
var oauthData = adobeDPS.oauthRedirectService.oauthData; if (oauthData === undefined || oauthData === null) { // Not initialized… console.log("Auth session not initialized! Call initAuthData()" } else { var refreshData = "grant_type=refresh_token"; refreshData += "&client_id=" + oauthConfig.clientId; refreshData += "&refresh_token=" + encodeURIComponent(oauthData.refresh_token); $.ajax({ url: oauthConfig.authURL + '/services/oauth2/token', headers: {'Authorization': 'Bearer ' + oauthData.refreshToken}, type: 'POST', data: refreshData, success: function (data, status, xhr) { // Reset oauthData in native storage. try { adobeDPS.oauthRedirectService.updateAuthData( data, function() { console.log(‘success: token refreshed’);}, error); } catch (error) { error(error); } }, error: function (xhr, status, error) { console.log("A server error occured"); } }); }
Revoke Current Authorization Token
var oauthData = adobeDPS.oauthRedirectService.oauthData; if (oauthData === undefined || oauthData === null) { // Not initialized… console.log("Auth session not initialized! Call initAuthData()" return; } var revokeUrl = oauthData.instance_url + '/services/oauth2/revoke'; var revokeData = "token=" + encodeURIComponent(oauthData.refresh_token); $.ajax({ url: revokeUrl, type: 'POST', cache: false, data: revokeData, contentType: 'application/x-www-form-urlencoded', success: function (data, status, xhr) { // Reset refreshToken and access_token in native storage. try { adobeDPS.oauthRedirectService.resetAuthData( resetSuccessCB, error); } catch (error) { error(error); } }, });

Collecting and Uploading Folio Interactions

Any data can be uploaded at any time to your SFDC account by adding the Authorization header to the AJAX transactions.
'Authorization': 'Bearer ' + <oauth_token>
In order to collect folio interaction events, you must first tell the viewer to start recording these events. This enables buffering and allows the events to be collected independent of internet connectivity. By default all events will be recorded, but you can specify an array of event names to filter the event capture with.
When connected, the following outlines the process.
  1. Enable recording
  2. Interact with folio
  3. Fetch events from the viewer using the API
  4. Upload these events to your SFDC account, as appropriate.
  5. Delete these events
  6. Repeat steps 2 through 5 as needed.
  7. Stop recording.
The only difference for off-line would be to postpone the fetch, upload and delete steps until connectivity is restored.
Start Recording
When starting recording, you need to pass in a configuration object indicating whether you want native UI notification of errors AND the list of events of interest. A null list of events indicates you want to receive all.
adobeDPS.folioActivityService.startRecording( recording_id // A user generated unique string , {enableErrorUIAlert:false, eventsFilter:events_filter} // Configuration , fas_error_handler) ; // Reference to error handler
Stop Recording
Retrieve Recorded Events
adobeDPS.folioActivityService.startRecording( recording_id // A user generated unique string , {enableErrorUIAlert:false, eventsFilter:events_filter} // Configuration , fas_error_handler) ; // Reference to error handler
'Authorization': 'Bearer ' + <oauth_token>
Sample Events Handler
The following will upload every event recorded into a custom SFDC object FolioEvent__c. SFDC Objects with the __c extension are user created, custom objects.
// Ensure that attributes also appear as custom for (var attr in folioEvent) { if (folioEvent.hasOwnProperty(attr)) { folioEvent[attr + "__c"] = folioEvent[attr]; delete folioEvent[attr]; } } // Tag the event to our ‘Opportunity’. This is a reference so the event can // be properly associated within SFDC folioEvent.opportunityId__c = opportunityId; var str = JSON.stringify(folioEvent); var oauthData = adobeDPS.oauthRedirectService.oauthData; if (oauthData && oauthData.access_token) { $.ajax({ type: 'POST', url: oauthData.instance_url + '/services/data/v20.0/sobjects/FolioEvent__c/', data: str, dataType: "json", cache: false, contentType: 'application/json', headers: { 'Authorization': 'Bearer ' + oauthData.access_token }, success: function (data, status, xhr) { console.log(‘successfully uploaded event’); }, error: function (xhr, status, error) { console.log("A server error occured"); } }); }

Getting Data from SFDC

Data is retrieved from SFDC via AJAX calls constructed with custom Authorization header.
'Authorization': 'Bearer ' + <oauth_token>
Otherwise, data is retrieved as with any other AJAX call. SFDC allows a DB Query structure in the calls so you can dynamically query data.
oauthData.instance_url + '/services/data/v20.0/query?q=SELECT+name,id+from+Opportunity+where+AccountId=\’foobar\'+order+by+name+asc'
Fetching a list of Opportunities for a specific Account foobar
var oauthData = adobeDPS.oauthRedirectService.oauthData; if (oauthData && oauthData.access_token) { $.ajax({ url: oauthData.instance_url + '/services/data/v20.0/query?q=SELECT+name,id+from+Opportunity+where+AccountId=\foobar\'+order+by+name+asc', cache: false, headers: { 'Authorization': 'Bearer ' + oauthData.access_token }, success: function (data, status, xhr) { for (var i = 0, count = data.records.length; i < count; ++i) { record = data.records[i]; console.log(record.Id + ',' + record.Name); } }, error: function (xhr, status, error) { // Do something }); }
Assuming your SFDC account contains an Opportunities Object, the above will print out all of the Opportunity records for the account foobar.

Where to go from here

Salesforce Connected Application Development
Adobe DPS Library and Store V2 SDK