Ajax calls working in Android but not iOS - ios

I'm developing an app in Nativescript for the first time and running into an issue where AJAX calls work on Android but not iOS. I have a login.js file which requires a user-view-model (user-view-model.js), and when I test the code on Android it takes me to the "home" page but it hits the catch function on iOS.
login.js:
var dialogsModule = require("ui/dialogs");
var UserViewModel = require("../../shared/view-models/user-view-model");
var applicationSettings = require("application-settings");
var user = new UserViewModel({
email: "aaa#aaa.com",
password: "aaa"
});
var frameModule = require("ui/frame");
var page;
exports.loaded = function(args) {
page = args.object;
page.bindingContext = user;
};
exports.login = function () {
user.login().catch(function(error) {
dialogsModule.alert({
message: "Unfortunately we could not find your account.",
okButtonText: "OK"
});
return Promise.reject();
}).then(function(response) {
console.dir(response)
console.log("past response")
applicationSettings.setString("user_id", response.user_id);
applicationSettings.setString("first_name", response.first_name);
applicationSettings.setString("last_name", response.last_name);
applicationSettings.setString("user_type", response.user_type);
var topmost = frameModule.topmost();
topmost.navigate("views/home/home");
});
};
user-view-model.js:
var config = require("../../shared/config");
var fetchModule = require("fetch");
var observableModule = require("data/observable");
var http = require("http");
function User(info) {
info = info || {};
var viewModel = new observableModule.fromObject({
email: info.email || "",
password: info.password || ""
});
viewModel.login = function() {
let loginEmail = JSON.stringify(this.get("email")).replace(/['"]+/g, '');
let loginPassword = JSON.stringify(this.get("password")).replace(/['"]+/g, '');
console.log(loginEmail, loginPassword);
let loginUrl = config.serverPHPServiceUrl + "Login.php?user_id=" + loginEmail + "&password=" + loginPassword;
console.log(loginUrl);
// I tried this way first and wasn't able to login on iOS, which made me try the second method below.
// return fetchModule.fetch(loginUrl, {
// method: "POST",
// headers: {
// "Content-Type": "application/json"
// }
// }).then(handleErrors).then(function(response) {
// return response.json();
// }).then(function(data) {
// console.dir(data);
// console.log(data["results"][0]["user_id"])
// return data["results"][0];
// });
// This method works on Android but not iOS.
return http.getJSON(loginUrl).then(function(response) {
console.dir(response);
return response.results[0];
})
};
return viewModel;
};
function handleErrors(response) {
console.log("in errors")
if (!response.ok) {
console.log(JSON.stringify(response));
throw Error(response.statusText);
}
return response;
}
module.exports = User;
Is there anything fundamentally wrong with my code, or do asynchronous calls work differently on iOS vs Android in Nativescript? I did the Grocery tutorial and didn't run into this issue, so I didn't think this was the case. Does it matter that the backend is using PHP?

I fixed my issue: I started a new project with Angular 2 and ran into the same error, but then it gave me the error message "Error: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." I solved it by adding "https" to my url call, but this post has another solution.

Related

ionic ios issue white page when doing REST Calls

I've got an ionic react app which fetches data from a rest API. When I bundle the ab with ionic server or on Android the app works fine.
When I run the same code on iOS I got a blank page. Some Versions before it worked on iOS as well. When I rebase my code the version 5 days ago now it doesn't work at all.
the code looks like this:
export default function PostsContainer() {
let {categoryid} = useParams<any>();
const [posts, setPosts] = useState<any[]>([]);
const [page, setPage] = useState<number>(1);
const [totPages, setTotPages] = useState<number>(1);
const [title, setTitle] = useState<string>("Recent posts");
const baseUrl = BASE_URL + "wp-json/wp/v2";
const [loadingPosts, setLoadingPosts] = useState<boolean>(false);
useEffect(() => {
async function loadPosts() {
setLoadingPosts(false)
let url = baseUrl + "/posts?status=publish&page=" + page;
if (categoryid !== undefined) {
url = url + "&categories=" + categoryid;
getCategoryName(categoryid);
}
const response = await fetch(url);
if (!response.ok) {
return;
}
const totalPages = await response.headers.get("x-wp-totalpages");
const postsTemp = await response.json();
setPosts(postsTemp);
setTotPages(Number(totalPages));
setLoadingPosts(true);
console.log(posts)
}
loadPosts();
}, [page, categoryid]);
function handleClickNextPage() {
setPage(page + 1);
}
async function getCategoryName(id: number) {
let url = baseUrl + "/categories/" + id;
const response = await fetch(url);
if (!response.ok) {
return;
}
const category = await response.json();
setTitle(category.name);
}
if (loadingPosts) {
return (
<IonPage>
<IonHeader translucent={true}>
</IonHeader>
<IonContent>
{page === 1 ? <Slider listOfPosts={posts}/> : <div/>}
<Posts
listOfPosts={posts}
totPages={totPages}
handleClickNextPage={handleClickNextPage}
pageNumber={page}
/>
</IonContent>
</IonPage>
);
} else {
return <IonLoading
isOpen={!loadingPosts}
message={ION_LOADING_DIALOG}
duration={ION_LOADING_DURATION}
/>
}
}
are there any settings belonging to the rest api? The call is to a URL with https. When I remove the useefect and only render a static page without content, it works fine.
At the End it was a CORS error on Endpoint. I had to allow cordova://localhost on Endpoint.

Multiple login windows/keeps re-prompting for username/password on acquireToken()

Every time I make a call to acquireToken, it keeps launching the AAD login window and prompts me for a username/password, even though I've already authenticated successfully and consumed an access token to make API calls.
Here is my code
Step 1. Call the loadData function from controller
loadData = (): Rx.IPromise<Array<UserResult>> => {
var url = this.xxxApiUrl;
return Http.get<Array<UserResult>>(this._$http, url);
};
Step -2
export function get<TResult>(http: ng.IHttpService, url: string,
ignoreLoadingBar: boolean = false, retryCount = 0): Rx.IPromise<TResult> {
var req: any = {};
if (ignoreLoadingBar) {
req.ignoreLoadingBar = ignoreLoadingBar;
}
let resObservable = Rx.Observable.create(subscriber => {
acquireToken(url, (message, token) => {
req.headers.Authorization = `Bearer ${token}`;
http.get(url, req)
.then(res => {
subscriber.onNext(res.data);
subscriber.onCompleted();
}, (err) => { alert(JSON.stringify(err)); });
});
});
return resObservable.toPromise();
}
function acquireToken(apiUrl: string, callback) {
let innerCallback = (res) => callback('', res.accessToken);
let xConfig= JSON.parse(<any>sessionStorage.getItem('xConfig'));
window.AuthenticationContext = new
window.Microsoft.ADAL.AuthenticationContext
(xConfig.common.azure.authorityTenant);
window.AuthenticationContext.tokenCache.readItems().then(items => {
if (items.length > 0) {
let authority = items[0].authority;
window.AuthenticationContext = new
window.Microsoft.ADAL.AuthenticationContext(authority);
}
let resourceUri = getResourceUri(xConfig, apiUrl);
window.AuthenticationContext.acquireTokenSilentAsync(resourceUri,
xConfig.common.azure.clientId, xConfig.common.azure.redirectUri)
.then(innerCallback, (err) => {
window.AuthenticationContext.acquireTokenAsync(resourceUri,
xConfig.common.azure.clientId, xConfig.common.azure.redirectUri)
.then(innerCallback);
});
});
}
Looking at your code, it looks like that you are using acquireTokenSilentAsync using the common endpoint, this is not supported. Please make sure to use your tenant Id or name (like tenant.onmicrosoft.com) instead of common when using acquireTokenSilentAsync
For more information about the common endpoint please see here

How to use Basic auth in swagger ui v.3.0

in swagger ui 2.0 it was code
var basicAuth = new SwaggerClient.PasswordAuthorization("basicAuth", username, password);
window.swaggerUi.api.clientAuthorizations.add("basicAuth", basicAuth);
Can somebody provide code for version swagger ui 3.0?
Thanks.
Edit.
i`m trying to do something like this - Adding Basic Authorization for Swagger-UI
I`m using Swagger on server with Basic auth. SO i cant init library.
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
// yay ES6 modules ↘
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
without basic auth everything works fine.
basic auth enabled - http://prntscr.com/enxee4
In Swagger UI 3.x, fetching specs (.yaml/.json files) protected with Basic auth / API keys is supported in ver. 3.3.2 and later. In your Swagger UI initialization code, define a requestinterceptor that attaches auth headers to the spec fetch request:
<!-- index.html -->
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
requestInterceptor: (req) => {
if (req.loadSpec) {
// Fetch the spec using Basic auth, replace "user" and "password" with yours
req.headers.Authorization = 'Basic ' + btoa('user:password');
// or API key
// req.headers.MyApiKey = 'abcde12345';
// or bearer token
// req.headers.Authorization = 'Bearer abcde12345';
}
return req;
},
...
})
I build an index.html with a simple form to fill user credentials to get a session id. Then redirect to swagger.html, for instance, and make some changes.
Before window.onload:
var orgFetch;
window.setExtraHeader = function(sessionId) {
var system = window.ui.getSystem();
if(!system) return;
if(!orgFetch) {
orgFetch = system.fn.fetch;
}
system.fn.fetch = function(obj) {
if(!obj) return;
if(!obj.headers) {
obj.headers = {};
}
obj.headers['sessionId'] = sessionId;
return orgFetch(obj)
.then(function(fetchRes) {
return fetchRes;
});
}
system.specActions.download();
}
and then:
window.ui = ui;
window.setExtraHeader(localStorage.getItem("sessionId"));
Source: https://github.com/swagger-api/swagger-ui/issues/2793
Hope this helps.
In swagger-ui 3.2x, to manually set Authorization header based on values entered in the authorize popup for basic authentication, we can use below code.
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
requestInterceptor: (req) => {
if (!req.loadSpec) {
var authorized = this.ui.authSelectors.authorized();
//'Basic_Authentication' is security scheme key for basic authentication in the OpenApi file
var basicAuth = getEntry(authorized, 'Basic_Authentication');
if (basicAuth) {
var basicAuthValue = getEntry(basicAuth, 'value');
if (basicAuthValue) {
var username = getEntry(basicAuthValue, 'username');
var password = getEntry(basicAuthValue, 'password');
if (username && password) {
req.headers.Authorization = "Basic " + btoa(username + ":" + password);
}
}
}
}
return req;
}
//traverse through the object structure of swagger-ui authorized object to get value for an entryName
function getEntry(complexObj, entryName) {
if (complexObj && complexObj._root && complexObj._root.entries) {
var objEntries = complexObj._root.entries;
for (var t = 0; t < objEntries.length; t++) {
var entryArray = objEntries[t];
if (entryArray.length > 1) {
var name = entryArray[0];
if (name === entryName) {
return entryArray[1];
}
}
}
}
return null;
}

Worklight JSON store encryption of collection not working in ios

I am using JSONStore in my application to store some sensitive data. To encrypt collection,we are passing options with username and password as mentioned below. In android so far its working fine but in ios devices we are getting blank page while retrieving data from collection (working fine in simulators). I'm not getting any errors also.Without passing options in ios, its working fine. Has anybody faced similar issue?
factory('todoJsonStorage',['$q', function ($q) {
'use strict';
var COLLECTION_NAME = 'Users';
var collections = {
Users: {
searchFields: {UserId: 'string', password: 'string'}
},
};
var options = {};
//Optional username
options.username = 'testuser';
//Optional password
options.password = 'test123';
//Optional local key generation flag
options.localKeyGen = true;
var inited = false;
//checks if inited and if not inits
function initJSONStore(){
var initDeferred = $q.defer();
if (inited){
initDeferred.resolve();
} else {
//Initialize the collection
WL.JSONStore.init(collections,options).then(function () {
console.log("-> JSONStore init successful");
initDeferred.resolve();
}).fail(function (errorObject) {
console.log("-> JSONStore error: " + errorObject.msg);
});
return initDeferred.promise;
};
}
return {
get: function () {
var deferred = $q.defer();
initJSONStore().then(function(){
WL.JSONStore.get(COLLECTION_NAME).findAll().then(function (res) {
if (res.length > 0){
deferred.resolve(JSON.parse(res[0].json.data || '[]'));
} else {
deferred.resolve(res);
}
}).fail(function (errorObject) {
console.log("JSONStore findbyid error: " + errorObject.msg);
});
});
return deferred.promise;
},
put: function (todos) {
WL.JSONStore.get(COLLECTION_NAME).clear();
WL.JSONStore.get(COLLECTION_NAME).add({data:JSON.stringify(todos)});
}
};
}])
If you are using iOS 10, you must enable the Keychain Sharing Capability, otherwise this should work out-of-the-box.

Different Titanium HTTPClient behavior from sdk 1.7.5 to 1.8.0.1

I found two different behaviors for Titanium HTTPClient in sdk 1.7.5 and 1.8.0.1
Here's my complete code:
function Synchronizer () {
//server calls
var callStack = [{
url: 'http://url1',
name: 'foo1'
},
{
url: 'http://url2',
name: 'foo2'
},
{
url: 'http://url3',
name: 'foo3'
},
{
url: 'http://url4',
name: 'foo4'
}
];
//processing data
var dataStack = new Array();
//call stack pointer
var stackPointer = 0;
//HTTPClient
var httpClient = Titanium.Network.createHTTPClient({
onerror:function(e){
UIManager.SynchronizeUI.onError();
},
ondatastream:function(e) {
UIManager.SynchronizeUI.setProgressBarValue(stackPointer);
}
});
//....
//DataConsumer
var DataConsumer = new DataConsumer();
//Method for synchronization
this.synchronizeAll = function (usr, pwd) {
if(!Ti.Network.online){
Ti.API.info('Not connected to the internet');
return;
}
Ti.API.info('Avvia syncro');
stackPointer = 0;
//Autentication call
httpClient.open('POST', 'http://url/login');
httpClient.onload = function() {
// If authenticated
if(this.responseText.trim() == ('OK,' + usr)) {
Ti.API.info('Login successful');
consumeCallStack();
}
//else
else {
//...
}
}
httpClient.send({username: usr, password: pwd, ajaxlogin: 'true'});
Ti.API.info('Calling login..');
}
/*
* stack consumer
*/
function consumeCallStack() {
Ti.API.info('Execute call ' + stackPointer);
httpClient.open('GET', callStack[stackPointer].url);
httpClient.onload = function(){
alert(httpClient.responseText);
//data
var data = JSON.parse(this.responseText);
Ti.API.info('Retrieved data for ' + callStack[stackPointer].name);
//..
}
httpClient.send();
}
HTTPClient retrieves the data and the on error function isn't involved.
The problem is that after being authenticated the HTTPClient second call gets the JSON that I want if running with 1.7.5 while gets the same successful login page of the first call with 1.8.0.1. May be a cookie not enabled problem? Thank you very much!
Use the release notes to see which bug fixes relate to your issue:
Titanium 1.8.0.1 Release Notes
httpclient sync mode
iOS: Http request sent twice on ios 5

Resources