I have a web app developed in node.js, and now I am starting to create a mobile app from it using capacitorjs. In the web app I used handlebars to parameterize views and then served them using res.render(). With capacitorjs, it seems that the way to proceed is to precompile the views (templates and partials), and just make AJAX calls to the server to get the data and then create html using Handlebars.template.view(data), etc. So far so good!
For the development phase I have the api server running on the local machine. I had been using a self-signed SSL certificate on the local machine, and it had given me no trouble for the past few months, after having previously added my own Certificate Authority's certificate to the keychain on my MacOS machine, and after accepting the development server's self-signed certificate in the browsers. With capacitorjs, after having organized web assets in a www directly and creating an index.html file, I started the process of creating the mobile app with iOS using:
npx cap add iOS
open ios/App/App.xcworkspace
I found that the iOS simulator does not like to access anything from an https server with a self-signed certificate despite the fact that I added my certificate authority's certificate to the device's Settings->General->About->Certificate Trust Settings and enabled full trust for root certificates (a list that includes my certificate authority's certificate). After struggling with this for a while, I decided to run my development server as http, at least initially to get the ball rolling. It still did not work, and I figured it is because of CORS, so I added this middleware to my app stack:
import cors from 'cors';
var corsOptions = {
origin: 'capacitor://localhost',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions));
This seems to work, although fleetingly! Here's a simple AJAX request I make to test whether I can retrieve the data from the development server:
document.getElementById('test').addEventListener('click', event => {
console.log('test clicked');
console.log(`apiServer: ${apiServer}`);
$.ajax({
url: `${apiServer}/api/test`,
type: 'get',
data: {},
success: function (data) {
console.log(`/api/test`, data)
},
error: function (err) {
console.log(`error: `, err);
}
});
});
Over on the server, the route looks like this:
router.get('/api/test', function(req, res, next) {
res.send({
title: 'API Test',
apiResponse: 'success',
});
});
After starting the iOS simulator, I get my app's home screen with the "test" button. When I click it the first time, it works fine. The log shows this:
⚡️ [log] - apiServer: http://192.168.1.101:3000
⚡️ [log] - /api/test {"title":"API Test","apiResponse":"success"}
Subsequent clicks, however, give an error:
⚡️ [log] - apiServer: http://192.168.1.101:3000
⚡️ [log] - error: {"readyState":0,"status":0,"statusText":"error"}
I am not sure why. I added a second API test route and hit that on the second click, and still get the same error. That is, the first GET request works, subsequent ones don't. Over on the server I can see that it responds with a status 200 for the first GET request, and sometimes also for the second GET request (whether I hit the same test route or two different routes), however, on the iOS simulator only the first hit gets the data, the second results in error. Subsequent GET requests (third, fourth, . . ) do not get registered by the server, and the simulator continues to give the same error:
⚡️ [log] - apiServer: http://192.168.1.101:3000
⚡️ [log] - error: {"readyState":0,"status":0,"statusText":"error"}
I am stuck, and could use help. Thanks for reading!
Update: I have tried various settings for Application Transport Security as recommended in various discussions on Stack Overflow and Apple Developer Forum, including:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
And, various combinations of these settings:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
<key>NSAllowsArbitraryLoadsInWebContent</key><true/>
<key>NSAllowsLocalNetworking</key><true/>
<key>NSExceptionDomains</key>
<dict>
<key>192.168.1.101:3000</key>
<dict>
<key>NSIncludesSubdomains</key><true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key<true/>
<key>NSExceptionRequiresForwardSecrecy</key><true/>
</dict>
</dict>
</dict>
But nothing seems to help. Surprisingly, if I set NSAllowsArbitraryLoads to NO, and remove all other keys in ATS, the behaviour remains the same. That is, the first AJAX request is successful, while subsequent ones are not and give the same error as earlier. ATS settings in Info.plist don't seem to matter!
CodePudding user response:
It seems like this wasn't an issue of permissions to access an external API server. I wrapped up the AJAX call within a function, makeRequest(), and just tried to execute it repeatedly using setInterval():
setInterval(() => { makeRequest(); }, 2000);
This worked: repeated GET requests to the API (whether on my local server at http://192.168.1.101:3000 or on an externally available public server such as https://emojihub.herokuapp.com/api/random) are now successful. So, something seemed to have been interfering with the AJAX requests when the button is clicked: never the first time when the simulator loads the app, but subsequent times. I then added event.preventDefault() to the top of the button event listener, and this has resolved the problem.