Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Jitendra_Kansal
Product and Topic Expert
Product and Topic Expert









For up-to-date information, please visit Mobile Services documentation. 




This blog is in continuation with https://blogs.sap.com/2017/08/29/uploading-offline-store-files-for-further-analysis-using-rest-api/ , here my intention is upload Offline DB files from a hybrid (kapsel) application itself to SAP Cloud Platform Mobile services (this can be done for on-premise SAP Mobile Platform 3.0 as well)

Here, I am running my project on an iPhone emulator.  (make sure you are done with all per-requisites as mentioned here)

 

 

Steps:

1. App on-boarding in SAP Cloud Platform Mobile Services for developer & Operations


1.1 Create a hybrid/native app (e.g. appID as com.kapsel.gs)



 

1.2 Make sure Security configuration is set to SAML (i also checked "Ignore Case for User Name")



 

1.3 Add Connectivity/destination to ES4 OData service , select SSO as Basic Auth (if you dont have access to this service , check here)



 

1.4 Enable Client DB upload feature



1.5 Create an .ini file , add below content , import this file under "Offline"
 [endpoint]
name=com.kapsel.gs
allow_omitting_max_length_facet=Y



 

2.  Creating a cordova/kapsel project using command line (reference)


2.1 Create a cordova project, add iOS platform, add required plugins

 
cordova plugin add kapsel-plugin-logon --searchpath $KAPSEL_HOME/plugins
cordova plugin add kapsel-plugin-logger --searchpath $KAPSEL_HOME/plugins
cordova plugin add kapsel-plugin-odata --searchpath $KAPSEL_HOME/plugins
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer

2.2 Replace index.html (/KapselGSDemo/www) content with below:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<script type="text/javascript" charset="utf-8" src="datajs-1.1.2.min.js"></script>
<script type="text/javascript" charset="utf-8" src="serverContext.js"></script>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script>
var applicationContext = null;
var online = false;
var store = null; //Offline OData store
var startTime = new Date();
var initTime = null;
var unlockTime = null;
var resumeTime = null;

window.onerror = onError;

function onError(msg, url, line) {
var idx = url.lastIndexOf("/");
var file = "unknown";
if (idx > -1) {
file = url.substring(idx + 1);
}
alert("An error occurred in " + file + " (at line # " + line + "): " + msg);
return false; //suppressErrorAlert;
}

function init() {
updateStatus2("Calling Logon.init");
initTime = new Date();
var endTime = new Date();
var duration = (endTime - startTime)/1000;
console.log("EventLogging: Time from onload to deviceready " + duration + " seconds");

if (sap.Logger) {
sap.Logger.setLogLevel(sap.Logger.DEBUG); //enables the display of debug log messages from the Kapsel plugins.
sap.Logger.debug("EventLogging: Log level set to DEBUG");
}

if (navigator.notification) { // Override default HTML alert with native dialog. alert is not supported on Windows
window.alert = navigator.notification.alert;
}

register();
console.log("EventLogging: init completed");
}

function logonSuccessCallback(result) {
updateStatus2("logonSuccessCallback called");
var endTime = new Date();
if (unlockTime) {
var duration = (endTime - unlockTime)/1000;
console.log("EventLogging: Unlock Time " + duration + " seconds");
unlockTime = null;
}

console.log("EventLogging: logonSuccessCallback " + JSON.stringify(result));
applicationContext = result;
showScreen("MainDiv");
}

function logonErrorCallback(error) { //this method is called if the user cancels the registration.
alert("An error occurred: " + JSON.stringify(error));
if (device.platform == "Android") { //Not supported on iOS
navigator.app.exitApp();
}
}

function read() {
updateStatus2("Read request started");
startTime = new Date();
if (!applicationContext) {
alert("Register or unlock before proceeding");
return;
}
clearTable();
sUrl = applicationContext.applicationEndpointURL + "/CarrierCollection?$format=json&$orderby=carrid"; //JSON format is less verbose than atom/xml
var oHeaders = {};
//oHeaders['X-SMP-APPCID'] = applicationContext.applicationConnectionId; //not needed as this will be sent by the logon plugin

var request = {
headers : oHeaders,
requestUri : sUrl,
method : "GET"
};

if (device.platform == "windows") { //provided by the authproxy and logon plugins on Android and iOS but not on Windows https://support.wdf.sap.corp/sap/support/message/1680272744
request.user = applicationContext.registrationContext.user;
request.password = applicationContext.registrationContext.password;
}
OData.read(request, readSuccessCallback, errorCallback);
}

function readSuccessCallback(data, response) {
var endTime = new Date();
var duration = (endTime - startTime)/1000;
updateStatus2("Read " + data.results.length + " records in " + duration + " seconds");
var carrierTable = document.getElementById("carrierTable");

for (var i = data.results.length -1; i >= 0; i--) {
var row = carrierTable.insertRow(1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
cell1.innerHTML = data.results[i].carrid;
cell2.innerHTML = data.results[i].CARRNAME;
}
}

function clearTable() {
var carrierTable = document.getElementById("carrierTable");
while(carrierTable.rows.length > 1) {
carrierTable.deleteRow(1);
}
}

function errorCallback(e) {
alert("An error occurred: " + JSON.stringify(e));
showScreen("MainDiv");
}

function register() {
updateStatus2("Calling Logon.init");
sap.Logon.init(logonSuccessCallback, logonErrorCallback, appId, context);
}

function uploadDB() {
var serverURL= "https://" + applicationContext.registrationContext.serverHost;
var uploadURL = serverURL + "/mobileservices/application/" + appId+ "/offlinestoreupload/v1/runtime/application/" + appId+ "/device/" + applicationContext.applicationConnectionId
// alert("hi johny");
var filesToCopy = [];
window.resolveLocalFileSystemURL(cordova.file.documentsDirectory, function(dataDirectoryEntry) {
var reader = dataDirectoryEntry.createReader();
reader.readEntries(function(entries) {

entries.forEach(function(entry) {
if (entry.name.endsWith('.udb')) {
alert("i found files");
filesToCopy.push(entry);
}
});


var successCallBack = function (r) {
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
}

var errorCallBack = function (error) {
alert("An error has occurred: Code = " + error.code);
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
}

var options = new FileUploadOptions();
options.fileKey = "file";
options.fileName = filesToCopy[0].name;
options.fileName1 = filesToCopy[1].name;
var ft = new FileTransfer();
ft.upload(filesToCopy[0].nativeURL, encodeURI(uploadURL), successCallBack, errorCallBack, options);
ft.upload(filesToCopy[1].nativeURL, encodeURI(uploadURL), successCallBack, errorCallBack, options);

});

})

}


function unRegister() {
showScreen("LoadingDiv");
updateStatus2("Calling deleteRegistration");
sap.Logon.core.deleteRegistration(logonUnregisterSuccessCallback, errorCallback);
clearTable();
}

function logonUnregisterSuccessCallback(result) {
updateStatus2("Successfully Unregistered");
console.log("EventLogging: logonUnregisterSuccessCallback " + JSON.stringify(result));
applicationContext = null;
register();
}

function lock() {
sap.Logon.lock(logonLockSuccessCallback, errorCallback);
clearTable();
}

function logonLockSuccessCallback(result) {
console.log("EventLogging: logonLockSuccessCallback " + JSON.stringify(result));
applicationContext = null;
showScreen("LockedDiv"); //sap.Logon.unlock(function () {},function (error) {}); //alternatively show the unlock screen
}

function unlock() {
unlockTime = new Date();
sap.Logon.unlock(logonSuccessCallback, errorCallback);
}

function managePasscode() {
sap.Logon.managePasscode(managePasscodeSuccessCallback, errorCallback);
}

function managePasscodeSuccessCallback() {
console.log("EventLogging: managePasscodeSuccess");
}

function showScreen(screenIDToShow) {
var screenToShow = document.getElementById(screenIDToShow);
screenToShow.style.display = "block";
var screens = document.getElementsByClassName('screenDiv');
for (var i = 0; i < screens.length; i++) {
if (screens[i].id != screenToShow.id) {
screens[i].style.display = "none";
}
}
}

function openStore() {
console.log("EventLogging: openStore");
startTime = new Date();
updateStatus2("store.open called");
var properties = {
"name": "CarrierOfflineStore",
"host": applicationContext.registrationContext.serverHost,
"port": applicationContext.registrationContext.serverPort,
"https": applicationContext.registrationContext.https,
"serviceRoot" : appId,
//"storePath" : cordova.file.externalRootDirectory,

"definingRequests" : {
"CarriersDR" : "/CarrierCollection"
}
};

if (device.platform == "windows") {
var authStr = "Basic " + btoa(applicationContext.registrationContext.user + ":" + applicationContext.registrationContext.password);
properties.streamParams = "custom_header=Authorization:" + authStr;
}

store = sap.OData.createOfflineStore(properties);
var options = {};
store.open(openStoreSuccessCallback, errorCallback, options, progressCallback);
}

function openStoreSuccessCallback() {
var endTime = new Date();
var duration = (endTime - startTime)/1000;
updateStatus2("Store opened in " + duration + " seconds");
updateStatus1("Store is OPEN.");
console.log("EventLogging: openStoreSuccessCallback. Stored opened in " + duration);
sap.OData.applyHttpClient(); //Offline OData calls can now be made against datajs.
}

function closeStore() {
if (!store) {
updateStatus2("The store must be opened before it can be closed");
return;
}
console.log("EventLogging: closeStore");
updateStatus2("store.close called");
store.close(closeStoreSuccessCallback, errorCallback);
}

function closeStoreSuccessCallback() {
console.log("EventLogging: closeStoreSuccessCallback");
sap.OData.removeHttpClient();
updateStatus1("Store is CLOSED.");
updateStatus2("Store closed");
}

//Removes the physical store from the filesystem
function clearStore() {
console.log("EventLogging: clearStore");
if (!store) {
updateStatus2("The store must be closed before it can be cleared");
return;
}
store.clear(clearStoreSuccessCallback, errorCallback);
}

function clearStoreSuccessCallback() {
console.log("EventLogging: clearStoreSuccessCallback");
updateStatus1("");
updateStatus2("Store is CLEARED");
store = null;
}

function refreshStore() {
console.log("EventLogging: refreshStore");
if (!store) {
updateStatus2("The store must be open before it can be refreshed");
return;
}
startTime = new Date();
updateStatus2("Store refresh called");
store.refresh(refreshStoreCallback, errorCallback, null, progressCallback);
}

function refreshStoreCallback() {
console.log("EventLogging: refreshStoreCallback");
var endTime = new Date();
var duration = (endTime - startTime)/1000;
updateStatus2("Store refreshed in " + duration + " seconds");
}

function flushStore() {
console.log("EventLogging: flushStore");
if (!store) {
updateStatus2("The store must be open before it can be flushed");
return;
}
startTime = new Date();
updateStatus2("Store flush called");
store.flush(flushStoreSuccessCallback, errorCallback, null, progressCallback);
}

function flushStoreSuccessCallback() {
console.log("EventLogging: flushStoreSuccessCallback");
var endTime = new Date();
var duration = (endTime - startTime)/1000;
updateStatus2("Store flushed in " + duration + " seconds");
refreshStore();
}

function viewLog() {
if (sap.Logger) {
sap.Logger.getLogEntries(getLogEntriesSuccess, errorCallback)
}
else {
alert("ensure the kapsel-logger plugin has been added to the project");
}
}

function getLogEntriesSuccess(logEntries) {
var stringToShow = "";
var logArray;

if (device.platform == "windows") {
logArray = logEntries.split("\n");
if (logArray.length > 0) {
for (var i = 0; i < logArray.length; i++) {
stringToShow += logArray[i] + "\n";
}
}
}
else if (device.platform == "iOS") {
logArray = logEntries.split("\n");
if (logArray.length > 0) {
for (var i = 0; i < logArray.length; i++) {
logLineEntries = logArray[i].split(" ");
for (var j = 7; j < logLineEntries.length; j++) {
stringToShow += logLineEntries[j] + " ";
}
stringToShow = stringToShow + "\n";
}
}
}
else { //Android
logArray = logEntries.split('#');
if (logArray.length > 0) {
var numOfMessages = parseInt(logArray.length / 15);
for (var i = 0; i < numOfMessages; i++) {
stringToShow += logArray[i * 15 + 1] + ": " + logArray[i * 15 + 3] + ": " + logArray[i * 15 + 14] + "\n";
}
}
}
alert(stringToShow);
console.log("EventLogging: Device Log follows " + stringToShow);
}


function updateStatus1(msg) {
document.getElementById('statusID').innerHTML = msg + " " + getDeviceStatusString();
console.log("EventLogging: " + msg + " " + getDeviceStatusString());
}

function updateStatus2(msg) {
var d = new Date();
document.getElementById('statusID2').innerHTML = msg + " at " + addZero(d.getHours()) + ":" + addZero(d.getMinutes()) + "." + addZero(d.getSeconds());
console.log("EventLogging: " + msg + " at " + addZero(d.getHours()) + ":" + addZero(d.getMinutes()) + "." + addZero(d.getSeconds()));
}

function addZero(input) {
if (input < 10) {
return "0" + input;
}
return input;
}

function getDeviceStatusString() {
if (online) {
return "Device is ONLINE";
}
else {
return "Device is OFFLINE";
}
}

function progressCallback(progressStatus) {
var status = progressStatus.progressState;
var lead = "unknown";
if (status === sap.OfflineStore.ProgressState.STORE_DOWNLOADING) {
lead = "Downloading ";
}
else if (status === sap.OfflineStore.ProgressState.REFRESH) {
lead = "Refreshing ";
}
else if (status === sap.OfflineStore.ProgressState.FLUSH_REQUEST_QUEUE) {
lead = "Flushing ";
}
else if (status === sap.OfflineStore.ProgressState.DONE) {
lead = "Complete ";
}
else {
alert("Unknown status in progressCallback");
}
updateStatus2(lead + "Sent: " + progressStatus.bytesSent + " Received: " + progressStatus.bytesRecv + " File Size: " + progressStatus.fileSize);
}

function deviceOnline() {
online = true;
updateStatus1("");
}

function deviceOffline() {
online = false;
updateStatus1("");
}


function onLoad() {
console.log("EventLogging: onLoad");
}

function onBeforeUnload() {
console.log("EventLogging: onBeforeUnLoad");
}

function onUnload() {
console.log("EventLogging: onUnload");
}

function onPause() {
console.log("EventLogging: onPause");
}

function onResume() {
resumeTime = new Date();
console.log("EventLogging: onResume");
}

function onSapResumeSuccess() {
console.log("EventLogging: onSapResumeSuccess");
var endTime = new Date();
var duration = (endTime - resumeTime)/1000;
console.log("EventLogging: Time from onresume to onSapResumeSuccess " + duration + " seconds");
}

function onSapLogonSuccess() {
console.log("EventLogging: onSapLogonSuccess");
}

function onSapResumeError(error) {
console.log("EventLogging: onSapResumeError " + JSON.stringify(error));
}



document.addEventListener("deviceready", init, false);
document.addEventListener("pause", onPause, false);
document.addEventListener("resume", onResume, false);
document.addEventListener("online", deviceOnline, false);
document.addEventListener("offline", deviceOffline, false);
document.addEventListener("onSapResumeSuccess", onSapResumeSuccess, false);
document.addEventListener("onSapLogonSuccess", onSapLogonSuccess, false);
document.addEventListener("onSapResumeError", onSapResumeError, false);



</script>

</head>
<body onload="onLoad()" onunload="onUnload()" onbeforeunload="onBeforeUnload()">
<div class="screenDiv" id="LoadingDiv">
<h1>Loading ...</h1>
</div>

<div class="screenDiv" id="LockedDiv" style="display: none">
<h1>Locked</h1>
<button id="unlock2" onclick="unlock()">Unlock</button>
</div>

<div class="screenDiv" id="MainDiv" style="display: none">
<h1>Offline Sample</h1>
<button id="read" onclick="read()">Read</button>
<button id="unregister" onclick="unRegister()">Unregister</button>
<button id="openStore" onclick="setTimeout(openStore, 10);">Open Offline Store</button>
<button id="closeStore" onclick="closeStore()">Close Offline Store</button>
<button id="clearStore" onclick="clearStore()">Clear Offline Store</button>
<button id="sync" onclick="flushStore()">Flush and Refresh</button>
<button id="clearLog" onclick="sap.Logger.clearLog();updateStatus2('Cleared the Log')">Clear Log</button><br>
<button id="viewLog" onclick="viewLog()">View Log</button>
<button id="uploadDB" onclick="uploadDB()">upload DB</button><br>
<span id="statusID"></span><br>
<span id="statusID2"></span>
<table id="carrierTable"><tr><th>Carrier ID</th><th>Carrier Name</th></tr></table>
</div>
</body>
</html>​



2.3 create a serverContext.js file under same www folder



var appId = "com.kapsel.gs"; // Change this to app id on server

// Optional initial connection context
var context = {
//"serverHost": "", //Place your SMP 3.0 server name here
"serverHost": "hcpms-xxxxtrial.hanatrial.ondemand.com", //SAP Cloud Platform Mobile Services
"https": true, //true for SAP Cloud Platform Mobile Services
"serverPort": "443", //443 for SAP Cloud Platform Mobile Services
//"multiUser": true,
//"useLocalStorage": false,
//"user": "xxxx", //For demo purposes, specify the user name and password you wish to register with here to save typing on the device
//"password": "xxxx", //Note, if you wish to use this user name and password to be passed to the backend OData producer, choose Basic as the SSO mechanism

"auth": [ { "type": "saml2.web.post" } ], //Indicates that a redirect to a SAML IDP should be used for registration
"refreshSAMLSessionOnResume": "skip", // Useful for offline apps when you may not wish for a saml check to be performed when the app is resumed since you may be offline

"custom": {
"hiddenFields": ["farmId", "resourcePath", "securityConfig", "serverPort", "https"]
}
};



2.4 Download datajs file from here and place it under www folder


2.5 Run cordova run ios --emulator --target iPhone-7 command


3. Upload DB from application running on iPhone emulator , go through 1-7 steps



  •    Login to app (authentication to SAP CP Mobile Services)

  •    Enable/disable passcode

  •    Read Online Data

  •    Open Offline Store

  •    Read Data from Offline store

  •    Close Offline Store

  •    Upload DB





4. Verify uploaded DB (files) in SAP CP Mobile Services



Click on "Offline" features, navigate to "Client Databases" , you will see two entries


(here are two database files, one named .udb and one name .rq.udb.
udb stands for UltraLite database and rq stands for request queue)







From here, Administrator can share these files to developers for further analysis. Look at step#11 mentioned in this blog how to do it.


Happy Learning & Sharing!!!


Jitendra Kansal

Product Management, SAP Cloud Platform User Experience
SAP SE