Note: Total Ionic newbie here.
I have the following:
Ionic 5 (Capacitor) app with Angular 11.
Express backend (localhost:3000)
I can fetch data from an API call and display in the browser, but not on the emulated Android device. I don't know how to check for console errors in Android Studio.
This image can explain the situation better.
I think this is due to CORS. I tried to follow the Ionic page on this but no resolution.
Here is my Express code:
const express = require("express");
const cors = require("cors");
const app = express();
const port = 3000;
const allowedOrigins = [
"capacitor://localhost",
"ionic://localhost",
"http://localhost",
"http://localhost:8080",
"http://localhost:8100",
"http://192.168.2.25:8100",
];
// For parsing JSON in request body
app.use(express.json());
// MySQL connection details - for POC sake.
// In PROD, these are typically saved in .env variables
// Ref: https://www.linkedin.com/pulse/storing-database-credentials-securely-siddhesh-jog
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "____________________________.us-east-2.rds.amazonaws.com",
user: "admin",
password: "*****************",
database: "poc",
});
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
console.log(origin);
callback(new Error("Origin not allowed by CORS"));
}
},
};
// Enable preflight requests for all routes
app.options("*", cors(corsOptions));
// Connect to MySQL
connection.connect(function (err) {
if (err) throw err;
console.log("Connected!");
});
// Dashboard - GET
app.get("/dashboard", cors(corsOptions), (req, res) => {
rows = [];
connection.query(
"select label_id, value from poc_fct",
function (err, result) {
if (err) throw err;
res.json(result);
}
);
});
app.listen(port, () => {
console.log(`CORS-enabled web server listening at http://localhost:${port}`);
});
Any help will be greatly appreciated.
What finally worked for me was changing the API endpoint from http://localhost:3000/data to http://192.168.2.25:3000/data, where 192.168.2.25 is the local IP address of the host where the Express server is running.
Few notes for anyone else who might have this issue in the future:
This isn't a CORS issue. I commented out app.use(cors)
This isn't a HTTP/HTTPS issue
Changing the emulator's proxy to 10.0.2.2:3000 did not work
Related
I am trying to set up a simple websocket connection in my React Native app. It is returning the following error in Android: {"isTrusted": false, "message": "Expected HTTP 101 response but was '401 Unauthorized'"}. The websocket is opening fine in iOS, and using a websocket connection that does not require my user to be authenticated works (my user is authenticated for this server but it still say I'm not authorized).
Here is the relevant code in my app:
useEffect(() => {
const ws = new WebSocket(`wss://${DOMAIN}/api/stream/all`)
ws.onopen = () => {
console.log('websocket open') // --> iOS
}
ws.onerror = e => {
console.log('error', e) // --> Android
}
}, [])
Changing it to this works on Android:
useEffect(() => {
const ws = new WebSocket(`wss://echo.websocket.events/.ws`)
ws.onopen = () => {
console.log('websocket open') // --> Android & iOS
}
ws.onerror = e => {
console.log('error', e)
}
}, [])
Any idea why authentication is not working? Is there some Android config that needs to be changed so the authentication that already happened for this domain on https gets to the websocket connection somehow?
I ended up changing it to this (added session cookie to headers for Android):
if (Platform.OS === 'ios') {
const ws = new WebSocket(`wss://${DOMAIN}/api/stream/all`)
ws.onopen = () => {
console.log('websocket open') // --> it works!
}
} else {
const ws = new WebSocket(`wss://${DOMAIN}/api/stream/all`, '', { headers: {Cookie: sessionCookie } })
ws.onopen = () => {
console.log('websocket open') // --> it works!
}
}
I got the session cookie from the response after the user logged in and kept that in state to be used in the websocket connection: const sessionCookie = res.headers.get('set-cookie'). This solution may not work for everyone if login doesn't work the same way for you, but it may help someone.
we are trying to connect the socket through https server and that was working perfectly fine few days back after that our ssl certificate expire on the server so we just use Let's encrypt to get the free ssl for our server and after that we ran into trouble.
And also we are using socket with socket.io-redis
package version in nodejs:-
"socket.io": "^1.7.3",
"socket.io-redis": "^4.0.0",
package version in android:-
io.socket:socket.io-client:0.8.3
our application is not connecting from the socket rest everthing is working fine.
one application we are getting the error of;
io.socket.engineio.client.EngineIOException: xhr poll error
Also i am providing the server side code please help me as soon as possible
backend in nodejs
client side android (application)
nodejs code
var https = require("https");
var server = https.createServer({
key: fs.readFileSync(process.env.SSL_KEY),
cert: fs.readFileSync(process.env.SSL_CERT),
}, app);
} else {
var server = http.createServer(app);
}
global.io = require('socket.io')(server,{ rejectUnauthorized: false });
var redis = require('socket.io-redis');
io.adapter(redis({
host: 'localhost',
port: 6478,
}));
io.on('connection', function (socket) {
console.log('...........................socket connected', socket);
}); ```
Please follow this demo code for setup socket io with node and express this may help you to fix your issue
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
console.log('a user connected');
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
I have this function signature on schema.graphql, can I used it in order to call to lambda function that retrieve user list from Cognito server?
type Query
{
echo(msg: String): String #function(name: "getUsers-${env}")
}
How can I call it from Android ?
Do I need Apollo ?
Does Amplify library it's enough ?
Basically you can't query users from Cognito Amazon server directly using the schema.
In the Android application you must create and use the following Amplify plugins, you can read more about it from here:
https://docs.amplify.aws/start/q/integration/android
You must create lambda function as describe here:
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18', region: 'eu-central-1'});
exports.handler = async (event) => {
// TODO implement
let users = [];
let roles = ['admin', 'user' ];
try
{
// (let i=0, len=roles.length; i<len; i++)
//{
//const role = roles[i];
let more = true;
let nextToken = '';
while (more)
{
let params = {
UserPoolId: "your pool id",
//GroupName: role,
Limit: 60
};
if (nextToken !== '')
{
params.NextToken = nextToken;
}
const rawUsers = await cognito.listUsers(params).promise();
const mapUsers = rawUsers.Users.map(user => {
let atts = {};
for (const att of user.Attributes)
{
atts[att.Name] = att.Value;
}
return {
username: user.Username,
name: atts.hasOwnProperty('name') ? atts.name : '',
email: atts.hasOwnProperty('email') ? atts.email : '',
status: user.UserStatus,
//role: role
};
});
users= users.concat(mapUsers);
if (rawUsers.hasOwnProperty('NextToken')) {
nextToken = rawUsers.NextToken;
} else {
more = false;
}
}
// }
const response = {
statusCode: 200,
// Uncomment below to enable CORS requests
// headers: {
// "Access-Control-Allow-Origin": "*"
// },
body: JSON.stringify(users),
};
return response;
}
catch(e)
{
const response = {
statusCode: 500,
// Uncomment below to enable CORS requests
// headers: {
// "Access-Control-Allow-Origin": "*"
// },
body: e,
};
return response;
}
};
Then create REST api:
Use the terminal Amplify CLI commands and connect it to the lambda function that was created including "Authenticated users only".
Run:
amplify add api
C:\DOV_AWS>amplify api add
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the
project: users
? Provide a path (e.g., /book/{isbn}):
C:\DOV_AWS>amplify api add
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the
project: DOV
? Provide a path (e.g., /book/{isbn}): /users
? Choose a Lambda source Use a Lambda function already added in the current Amplify projec
t
? Choose the Lambda function to invoke by this path getUsers
? Restrict API access Yes
? Who should have access? Authenticated users only
? What kind of access do you want for Authenticated users? create, read, update, delete
? Do you want to add another path? No
Successfully added resource DOV locally
Use the amplify push command:
amplify push
In order to update the API on the cloud.
Run the following code in your app in order to fetch the users.
RestOptions options = RestOptions.builder()
.addPath("/users")
.build();
Amplify.API.get("Users", options, response ->
Log.i("MyAmplifyApp", " ! ! ! ! ! Data Respond ! ! ! ! !"
+ response.getData().asString()),
error -> Log.e("MyAmplifyApp", "GET failed", error)
);
You must add permission rule for Cognito server in the lambda function in order to fetch the user data.
The authentication method will include IAM rule
I am trying to implement a client-server communication for my Ionic 4 android app. I have a local node js server implementation on my pc and I am using socket.io to establish the communication. The problem is that when I am running the app on browser or on emulator the communication between the app and local server is ok but when I am trying to run the app on a real android device (samsung galaxy s9+) the app cannot connect to the local server. More specific the message 'User Connected' is not appeared on the server console to indicate that app is connected to the server and therefore user never gets back his userid.
Can anyone help me to find out why?
Server side
index.js
let app = require('express')();
let server = require('http').createServer(app);
let io = require('socket.io')(server);
// Allow CORS support and remote requests to the service
app.use(function(req, res, next)
{
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,Authorization');
next();
});
// Set up route
app.get('/', (req, res) =>
{
res.json({ message: 'You connected to panic Shield server' });
});
var port = process.env.PORT || 8080;
server.listen(port, function(){
console.log('listening in http://192.168.x.x:' + port);
});
const uuidv4 = require("uuid/v4");
// User connected to the server
io.on('connection', (socket) => {
console.log('User Connected');
socket.on('disconnect', function(){});
socket.on('set-credentials', (credentials) => {
let userId = uuidv4();
...
// server informs user for his unique id
socket.emit('your user id', userId);
...
}
});
Client side
app.module.ts
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
const config: SocketIoConfig = { url: 'http://192.168.x.x:8080', options: { secure: true } };
#NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
SocketIoModule.forRoot(config)
],
...
home.page.ts
import { Socket } from 'ngx-socket-io';
constructor(private socket: Socket) { }
async ngOnInit() {
this.socket.connect();
...
}
async connectToServer(name, surname, gender, speech) {
const str = name.concat(' // ').concat(surname).concat(' // ').concat(gender).concat(' // ').concat(speech);
this.socket.emit('set-credentials', str);
this.socket.on('your user id', (data) => {
this.userId = data.toString();
// save the userId
set('userId', this.userId);
// Refresh the page
window.location.reload();
});
}
I found the problem. In my case, the firewall of my pc doesn't allow any other device on local network connect to the server. When I turned off the firewall the connection between client-server was established!
I am using a custom HTTP request class for adding a Authorization Header to all of my requests, this works fine on almost every android device. Wired thing now is that I got some customer complaints that they are getting the 'No internet connection' error although they have a working network connection (other apps work and the errors are transmitted to the Sentry servers also).
As I am using Sentry error tracking I found out that these customers are all getting the error because the timeout error is thrown after 10 seconds for the first request at app start.
I guessed that something has to be wrong with this request so I built an alpha version for a limited number of users to track down the error (I send the options of every request to Sentry), but the requests look fine.
Next guess was that something is wrong with cordova-plugin-nativestorage on these devices but as I am catching them it should at lease return an empty token. No clue how to fix it right now. Any advice is appreciated!
export class CustomRequest {
apiToken: string = '';
constructor(private http: Http) { }
protected request(options: any): Observable<any> {
// If Native Storage doens't find a token, return an empty
let errorNativeStorage$ = function (): Observable<any> {
return Observable.of({ Token: '' });
};
// Get Token form Native Storage
let token$ = Observable.fromPromise(NativeStorage.getItem('JWT'))
.catch(errorNativeStorage$);
// Handle request errors
let genericError$ = function (error: Response | any): Observable<any> {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
Raven.captureException(error, { extra: { errorMsg: errMsg } });
return Observable.of({
Errors: { General: 'No internet connection.' }
});
};
// the request
let request$ = (options) => {
return this.http.request(new Request(options))
.retryWhen(error => error.delay(1000))
.timeout(10000, new Error('timeout'))
.map((res: Response) => res.json())
.catch(genericError$);
};
// get the token and build request
return token$
.map(jwt => {
if (options.body) {
if (typeof options.body !== 'string') {
options.body = JSON.stringify(options.body);
}
options.body = options.body.replace(/ /g, '').replace(/\r?\n|\r/g, '');
}
options.headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded, application/json'
});
if (jwt.Token) {
options.headers.append('Authorization', `Bearer ${jwt.Token}`);
}
Raven.captureMessage('request options', { level: 'info', environment: 'live', extra: options });
return options;
})
.switchMap(options => request$(options));
}
}
I am using:
Ionic 2.0.0-beta.11
Angular 2.0.0-rc.4
Most recent version of NativeStorage plugin from github
Devices with the error (only two examples, there are more):
Samsung SM-N910F (Webview: Chrome Mobile 53.0.2785, Android 6.0.1)
Samsung SM-G800F (Webview: Chrome Mobile 53.0.2785, Android 5.1.1)
If somebody's interested: The root cause was that people that upgraded Android somehow lost the chrome webview app and Angular was not working without one (of course). I solved it by packaging the crosswalk-webview in my app!