function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
juhanajuhana 

Hybrid remote app Logout problem

I have developed HTML5 app which is working just fine in browser.

 

Now I'm looking for Hybrid Remote app (android & iOS). I created connected app to SF, URL is pointing to my HTML5 app. Hybrid remote client starts fine, shows login and then goes fine to my app. Working perfectly to this stage.

 

But then, my app have logout button which redirects to /secur/logout.jsp, with browser this works as expected. With hybrid app, it logouts and shows the login screen again but if I now try login again, it opens to SF home page and not my HTML5 what I expected. Also, if I restart hybrid app, it goes straight to HTML5 app so looks like the hybrid app did not logout.

 

How I should implement the logout so it will logout the hybrid client. Can I somehow listen URL where hybrid app webview is going ?

 

Best Answer chosen by Admin (Salesforce Developers) 
bhariharanbhariharan

You can include Cordova and the Mobile SDK Javascript libs as static resources in your org, and access them through your VF page on the cloud. You'll need cordova-2.3.0.js, cordova.force.js, and the jquery folder/libs.

All Answers

bhariharanbhariharan

You'll need to configure your logout button to call the logout() method in the Mobile SDK. This will log the user out and take you back to the auth screen.

 

var sfOAuthPlugin = cordova.require("salesforce/plugin/oauth");
sfOAuthPlugin.logout();

 

juhanajuhana

Cordova is client side library ?

 

All my codes are in cloud, mobile client is hybrid remote so there is no content or code in mobile.

bhariharanbhariharan

You can include Cordova and the Mobile SDK Javascript libs as static resources in your org, and access them through your VF page on the cloud. You'll need cordova-2.3.0.js, cordova.force.js, and the jquery folder/libs.

This was selected as the best answer
niki4810niki4810
Hi @bhariharan,

This solution does not seem to work, I have a added the code you suggested into a button click handler for a to a visual force page.


When i click button, my application does not logout, nor my token gets revoked?

Could you please share some code that is working, so that I can use it as a reference.

Thanks,

Nikhil
bhariharanbhariharan

Please look at my response above.

 

var sfOAuthPlugin = cordova.require("salesforce/plugin/oauth");
sfOAuthPlugin.logout();

 You'll need to include the required JS libs as static resources in your org as well, and include them in your VF page, namely cordova.force.js, cordova-2.3.0.js, and jQuery libs, if you use them.

niki4810niki4810
I did exactly the same thing, but it does not seem to work, here is my complete code on my visualforce page


<apex:page docType="html-5.0"
showHeader="false" sidebar="false"
standardController="Contact">
<apex:stylesheet value="{!URLFOR($Resource.force_templates,'css/app.css')}"/>
<apex:includeScript value="{!URLFOR($Resource.MobileSample_Resources_jQueryMobile, 'jquery-1.9.1.min.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.force_templates,'js/main.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.force_external, 'cordova-2.3.0.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.force_libs, 'cordova.force.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.force_external, 'backbone/underscore-1.4.4.min.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.force_libs, 'force.entity.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.SObjectData)}"/>
<head>
<title>Address Book</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<c:RemoteTK />
<style>
#detailview {display: none;}
.list-view li { padding-left: 0px; margin-left: 0px; }
</style>
&lt;script type="text/javascript">
// var $j = jQuery.noConflict();

var client = new remotetk.Client();
Force.init(null,null,client,null);

var Contacts = new SObjectData();
Contacts.errorHandler = displayError;

var addressBook = new Array();

var logToConsole = cordova.require("salesforce/util/logger").logToConsole;
var contactsObj = cordova.require("cordova/plugin/contacts");

$(document).ready(function() {
//Add event listeners and so forth here
logToConsole("onLoad: jquery ready");
$('#back-button').hide();
$('#back-button').click(function(e){
changePage();
});

$('#logout-button').click(function(e){
handleLogout(e);
});

$('#search-text').focus(function(e){
$('#search-text').val('');
});
$('#search-text').blur(function(e){
searchContacts($('#search-text').val());
});
$('#save-salesforce').click(function(e){
syncToSalesforce();
});
$('#save-addressbook').click(function(e){
syncToAddressBook();
});
getAllContacts();
});

function handleLogout(e){
e.preventDefault();
var sfOAuthPlugin = cordova.require("salesforce/plugin/oauth");
sfOAuthPlugin.logout();
}

function getAllContacts() {
logToConsole('fetching data');
Contacts.fetch('soql',"SELECT id, firstName, lastName, phone from Contact WHERE phone != '' LIMIT 100",function() {
showContacts(Contacts.data());
});
if(cordova) {
logToConsole('fetching address book');
var contactOptionsType = cordova.require("cordova/plugin/ContactFindOptions");
var options = new contactOptionsType();
options.filter = ""; // empty search string returns all contacts
options.multiple = true;
var fields = ["name","phoneNumbers"];
contactsObj.find(fields, showAddressBook, displayError, options);
};
}

function checkContacts(search_text) {
$('#save-salesforce').hide();
$('#save-addressbook').hide();
var ContactCheck = new SObjectData();
ContactCheck.fetch("soql","SELECT id, firstName, lastName, phone from Contact WHERE Name LIKE '%"+search_text+"%'",function() {
if(!ContactCheck.dataObject) {
$('#save-salesforce').show();
}
});
if(cordova) {
logToConsole('fetching address book');
var contactOptionsType = cordova.require("cordova/plugin/ContactFindOptions");
var options = new contactOptionsType();
options.filter = search_text; // empty search string returns all contacts
// options.multiple = true;
var fields = ["name","phoneNumbers"];
contactsObj.find(fields, function(records){if(records.length == 0){$('#save-addressbook').show();}}, displayError, options);
};

}


function searchContacts(search_text) {
Contacts.fetch("soql","SELECT id, firstName, lastName, phone from Contact WHERE Name LIKE '%"+search_text+"%'",function() {
showContacts(Contacts.data());
});

if(cordova) {
logToConsole('fetching address book');
var contactOptionsType = cordova.require("cordova/plugin/ContactFindOptions");
var options = new contactOptionsType();
options.filter = search_text; // empty search string returns all contacts
// options.multiple = true;
var fields = ["name","phoneNumbers"];
contactsObj.find(fields, showAddressBook, displayError, options);
};
}

function changePage() {
$('#listview').toggle();
$('#detailview').toggle();
$('#back-button').toggle();
$('#search-text').toggle();
}

function showContacts(response) {
logToConsole('contacts found');
$('#sfdc-list').empty();
if(Contacts.data() == null) { return; }
$.each(Contacts.data(),
function() {
var newLi = $('<li></li>');

var newLink = $('<a class="content" id="' +this.Id+ '" ><h2>' +this.FirstName+ ' '+this.LastName+ '</h2></a> <div class="list-view-icons"><span class="icon-right-arrow">&nbsp;</span></div>');
newLink.click(function(e) {
e.preventDefault();
$('#first-name').html(Contacts.findRecordById([this.id]).FirstName);
$('#last-name').html(Contacts.findRecordById([this.id]).LastName);
$('#phone').html(Contacts.findRecordById([this.id]).Phone);
$('#phone').attr('href','tel:'+Contacts.findRecordById([this.id]).Phone);
$('#contact-id').val(Contacts.findRecordById([this.id]).Id);
checkContacts($('#first-name').html() + ' ' + $('#last-name').html());
changePage();
});
newLi.append(newLink);
newLi.appendTo('#sfdc-list');
});
}

function showAddressBook(records) {
logToConsole('address book found');
logToConsole("onSuccessDevice: received " + records.length + " contacts");
addressBook = records;
$('#book-list').empty();
$.each(records, function(i, contact) {
var formattedName = contact.name.formatted;
var phone;
if(contact.phoneNumbers && contact.phoneNumbers.length >0) {
phone = contact.phoneNumbers[0].value;
} else {
phone = '';
}
var newLi = $('<li></li>');
var newLink = $("<a index='"+i+"' fname='"+contact.name.givenName+"' lname='"+contact.name.familyName+"' phone='"+phone+"'><h2>" + formattedName +"</h2></a> <div class='list-view-icons'><span class='icon-right-arrow'>&nbsp;</span></div>");
newLink.click(function(e) {
e.preventDefault();
$('#first-name').html($.attr(this,'fname'));
$('#last-name').html($.attr(this,'lname'));
$('#phone').html($.attr(this,'phone'));
$('#phone').attr('href','tel:'+$.attr(this,'phone'));
$('#contact-id').val($.attr(this,'index'));
checkContacts($('#first-name').html() + ' ' + $('#last-name').html());
changePage();
});
newLi.append(newLink);
newLi.appendTo('#book-list');
});
}

function syncToSalesforce() {
console.log('Saving to SFDC');
var Contact = new SObjectData();
Contact.fetch('soql',
"SELECT ID, FirstName, LastName, Phone from Contact WHERE FirstName='"+$('#aFName').val()+"' AND LastName='"+$('#aLName').val()+"' LIMIT 1",function() {
console.log('return val:');
var record;
if(Contact.record() == null) {
console.log('creating record');
record = Contact.create('Contact',{
'FirstName' : $('#first-name').html(),
'LastName' : $('#last-name').html(),
'Phone' : $('#phone').html(),
});
} else {
console.log('updating record');
record = Contact.record();
}
console.log('Syncing!!');
Contact.sync(function() {
getAllContacts();
checkContacts($('#first-name').html() + ' ' + $('#last-name').html());
});
});

}

function syncToAddressBook() {
console.log('saving local address');
var contact = contactsObj.create();
contact.name = {
'givenName' : $('#first-name').html(),
'familyName' : $('#last-name').html()
};
var phoneNumbers = [];
phoneNumbers[0] = new ContactField('work', $('#phone').html(), true);
contact.phoneNumbers = phoneNumbers;
contact.save(function() {
getAllContacts();
checkContacts($('#first-name').html() + ' ' + $('#last-name').html());
},displayError);
}



//success and error callbacks
function displayError(e){
logToConsole('ERROR FOUND:');
logToConsole(e);
// $j('#error').html(e[0].message);
}
&lt;/script&gt;
</head>
<body>
<div id="app-wrapper">

<nav id="main-menu" class="main-menu">
<a href="#">Something</a>
<a href="#">Something</a>
<a href="#">Something</a>
</nav><!--#main-menu-->

<header>
<div id="main-menu-button-left" class="main-menu-button main-menu-button-left"><a id="back-button" class="left-arrow">&nbsp;</a></div>

<h1>Phone Book</h1>
<div id="main-menu-button-right" class="main-menu-button main-menu-button-right"><a id="logout-button">Logout</a></div>
</header>

<div id="app-content">
<input id="search-text" type="text" value="Seach Contacts" />

<div id="listview">
<div class="list-view-header"><h2>Salesforce Contacts</h2></div>
<ul id="sfdc-list" class="list-view">
</ul>

<div class="list-view-header"><h2>Address Book</h2></div>
<ul id="book-list" class="list-view">
</ul>
</div>

<div id="detailview">
<section class="border-bottom">
<div class="content">
<h2 id="first-name">Hello World</h2>
<h2 id="last-name"></h2><br />
<a id="phone" class="action-phone">&nbsp;</a>
<input id="contact-id" type="hidden" />
</div><!--.content-->
</section>
<div class="form-navigation-buttons form-navigation-buttons-relative">
<div class="form-navigation-buttons-padding">
<a href="#" id="save-salesforce" class="span-100 next">Save To Salesforce</a>
</div>
<div class="form-navigation-buttons-padding">
<a href="#" id="save-addressbook" class="span-100 next">Save To Address Book</a>
</div>
</div>
</div><!--.detail-view-header-->


</div><!--#app-content-->
</div><!--#app-wrapper-->
</body>
</apex:page>
niki4810niki4810
@bhariharan,

Just wanted to follow up on this issue ? Does my code look correct?
-
Nikhil
bhariharanbhariharan

At a cursory glance, it looks okay to me. However, I'd recommend debugging using log statements at different points, starting with the logout method, and going further into the plugin's logout method. This exercise will help you determine how far it goes, and if the SDK's logout is actually being called.

niki4810niki4810

@bhariharan

 

I was finally able to get the logout functionality working. However, I did hit an issue with refreshing my access token. 

 

Consider the following scenario

 

1) Initial launch of the application - > Redirect to SFDC login page

2) After login and allowing permission - > Redirect to my visual force page

3) From this point I am able to query data and load it successfully. (Even logging out works fine)

4) After a significant amount of time, i.e. when my initial access token has experied, if I try to make a request, I see a loading error.

5) When I  kill my app from background and re launch my app I am redirect to SFDC login page but no matter how many times I login, i am still shown the SFDC login page.

 

At this point, I have to uninstall and reinstall the app to make it work (and my previous login is not revoked yet)

 

Do you have a / Did you come across an example that demonstrates the entire oAuth flow for hybrid_remote application. i.e.

1) Handle login

2) Handle logout

3) Handle refreshing the oAuth acess token.

 

If so could you please point me to the repo or project page that I can use as a reference. I have come across this repo : https://github.com/forcedotcom/CloudHunter but the code seems to be outdated. 

 

Thanks,

Nikhil

 

bhariharanbhariharan

We put a fix in for the refresh scenario recently. Please try it with the latest here.

Ravi NarayananRavi Narayanan
Hi,

I am having problems in creating a sample hybrid remote app. i followed all the steps. but getting error,index.html is missing .can someone help me pl
Sowmya M NSowmya M N
Hi All,
We are facing the same issue - Unable to login after session timeout. Using SDK6.0
Please let me know how did you solve this issue.
Regards,
​Sowmya