Sencha App with Salesforce API using a Sinatra Proxy


In my last article, I presented the Sencha Framework and some of the advantages this product has over other mobile solutions. Convinced that I had a wonderful tool in my hands I decided to create a simple app. The aim is to have an app with two tabs. The first with an intro page and the second with a list of leads retrieved through the Salesforce REST API. The list must be sorted and provide basic interaction by displaying additional details of a selected lead. As soon as you start playing with mobile applications and a remote API you have to face a common javascript limitation: the Same Origin Policy. You may like to read the Wikipedia article if you want to know more about that rule. Briefly, “The policy permits scripts running on pages originating from the same site to access each other’s methods and properties with no specific restrictions, but prevents access to most methods and properties across pages on different sites”. Said differently, your cute javascript mobile application hosted under xyz.com domain _can not_ request any data from salesforce.com domain. Such a shame but imperative to keeping the web safer.

If you browse the web you’ll find a couple of ways to load remote data. Here is a quick list to introduce you to possible solutions or to refresh your memory :

  • Proxy your request: a common pattern to mimic same origin requests is to proxy calls from your javascript application using a service hosted on your initial domain. Pat Patterson released a modified version of Simple proxy for Force.com. Really useful but so PHP for me 🙂
  • Host your Sencha app on Force.com: right before Dreamforce 12, the Sencha Team published a really detailed 2-parts blog post explaining how to create a Sencha app direclty on Force.com. As a consequence, your application is generated from Force.com, is hosted on a Force.com site and can provide Salesforce data straight into the Sencha app.
  • Implement CORS: CORS (Cross-Origin Resource Sharing) is a recent solution to allow cross domain requests. The solution is robust and standardised but unfortunately requires you to have the ability to white-list your originating domain on the target server. That’s not possible with Salesforce.
  • Implement JSONP: JsonP is a way to perform requests on a remote server. That option is limited to GET and implies that you implement a simple “service” (a js function wrapping your call) on the remote server.

It may seem that in a Salesforce context you can either use a Proxy or implement your Sencha app on a Force.com site. But is there another option? As a Ruby developer, I finally came up with a hybrid solution: build an endpoints service with Sinatra. Sinatra is a perfect companion to build simple web services. Mixed with databasedotcom gem to easily access Salesforce REST API, you can write, in less than 50 lines, a service able to proxy your requests. The main advantage of the solution is that, before sending results back to your Sencha app, you can;

  • Pre-process data in Ruby: computed fields, grouping, sorting data
  • Perform caching: include a redis cache or a standard caching solution
  • Mix data from various sources
  • Reduce payload size reducing json to its strict minimum

Furthemore, the Sencha app is served by the Sinatra app that embed it. To clarify, here is the structure of the app folder: You can see the Sencha app is under the asset/ folder of the Sinatra app. The Sinatra app’s 3 main actions are:

  • Serve an index page for Salesforce Oauth session opening and manage sessions
  • After a successful authentication, redirect the user to the Sencha app
  • Serve data from Salesforce REST API through the /leads.json URL
Below you can see the Sinatra app code. It’s divided into 5 sections:
  1. Load libs and gems
  2. Config the app. We use Rack session cookies to store the token and connection to Salesforce ORG , path to assets (where the Sencha app code stands) is defined and we setup the configuration for oauth based on a salesforce.yml file
  3. Basic routing to respond to / and /home. /home view load the Sencha app
  4. OAuth Callback Management
  5. Json Data Store, the homemade proxy collecting Leads from Salesforce REST API and formatting the output json
#!/usr/bin/env ruby -I ../lib -I lib
  # SECTION 1 : load libs and gems
  require 'sinatra'
  require "sinatra/content_for"
  require 'sinatra/json'
  require 'haml'
  require 'databasedotcom'
  require 'yaml'
  require 'omniauth'
  require 'omniauth-salesforce'

  # SECTION 2: config the app
  use Rack::Session::Cookie
  set :public_folder, File.dirname(__FILE__) + '/assets'
  config = YAML.load_file("config/salesforce.yml") rescue {}
  use OmniAuth::Builder do
    provider :salesforce, config["client_id"], config["client_secret"]
  end

  # SECTION 3: basic routing for / and /home 
  get '/' do
    haml :intro
  end
  get '/home' do
    haml :home
  end

  # SECTION 4: OAuth Callback management
  get '/auth/salesforce/callback' do
     session[:token] = request.env['omniauth.auth']['credentials']['token']
    config = YAML.load_file("config/salesforce.yml") rescue {}
    dbdc = Databasedotcom::Client.new(:client_id => config["client_id"], :client_secret => config["client_secret"])
   dbdc.authenticate :token => session[:token], :instance_url => "http://na14.salesforce.com"
    session['client'] = dbdc
    redirect '/home'
  end

  # SECTION 5: Json Data store for the Sencha app
  get '/leads.json' do
    content_type :json
    session['client'].materialize('Lead')
    leads = Lead.all
    leads.collect! { |obj| {
                        :id    => obj.Id,
                        :name  => obj.LastName,
                        :email => obj.Email}
                      }.to_json
  end

You can access that sample application on Github: https://github.com/vzmind/sencha-salesforce

The next part of the Sencha series will be an article about the Sencha code itself and how to generate a standalone iPhone app with offline capabilities out of that example.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: