I'm trying to do an HTTP request on an android emulator, I built with ionic and compiled with capacitor.
I'm using the HTTP module from #ionic-native/http/ngx. I have a per-existing interceptor that I used when I was making requests using the HttpClient from #angular/common/http. This is what that interceptor looks like:
export class InterceptorService implements HttpInterceptor {
protected apiUri = environment.API_URL;
constructor(
private authSvc: AuthService,
private toastCtrl: ToastController
) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const url = this.apiUri + req.url;
const token = this.authSvc.getStorageToken();
if (token) {
req = req.clone({
url,
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
}
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
return event;
}),
catchError( (err: HttpErrorResponse) => {
let errMessage = err.error.message;
if (err.error.title) {
errMessage = err.error.title;
}
if (!errMessage) {
errMessage = err.error;
}
this.presentToast(err.status, errMessage);
return throwError(err);
})
);
}
async presentToast(status: number, message: string) {
const toast = await this.toastCtrl.create({
message,
buttons: ['OK']
});
await toast.present();
}
}
My auth-service:
/* service request to get a toke */
getAuthToken(model: IGetTokenModel) {
return this.http.post('/user/token', model, {});
}
The environment.API_URL is the url to my API on my local machine.
I'm guessing the no protocol means I'm posting data to /user/auth instead of the full which includes the hostname. But I'm not sure of this. However, if that were the case, how would I refactor my interceptor so that it works as expected with the #ionic-native/http/ngx?
Finally have this sorted. I was pointing to localhost inside the emulator. Turns out that requests made inside the emulator have to point to http://10.0.2.2:PORT.
I suppose localhost in a sense still point to the emulator itself.
Related
I make several requests from a React Native app to an API. Every request works fine both on iOS and Android except the DELETE method that does not work on Android. The call is correctly made, it goes through the API and the objects are deleted. But instead of getting the response, the call falls under the catch statement with [TypeError: Network request failed]. This does not happen in iOS.
Some people with the same problem were missing 'Content-Type': 'application/json' on the request headers which is not my case.
This is happening both locally, in testing and production stages (using an ip instead of localhost will do nothing).
The request is also successfully performed in Postman.
What can it be?
React Native 0.63.5
export const deleteApi = async (api: string, body?: any) => {
const userResponse = await getUserCredentials();
const authState = await getAuthState();
let response = await fetch(api, {
method: 'DELETE',
headers: await getHeaders(userResponse, authState),
body: JSON.stringify(body)
});
if (response.status === UNAUTHENTICATED_CODE)
response = await interceptor(response, userResponse, {
api: api,
method: 'DELETE',
body: body
});
return response;
};
leaveClass = async (
uuid: string,
onSuccess: () => void,
onFailure: (error: string) => void,
) => {
this.setLoading(true);
try {
const api = LEAVE_CLASS_API_PREFIX + uuid + LEAVE_CLASS_API_SUFFIX;
const response = await deleteApi(api);
if (response.status === SUCCESS_STATUS_CODE) {
onSuccess();
}
else {
const jsonResponse = await response.json();
if (jsonResponse.detail) onFailure(jsonResponse.detail);
else onFailure(translations.SOMETHING_WENT_WRONG);
}
} catch (error) {
console.log('leaveClass error: ', error);
}
this.setLoading(false);
};
You can use a network plugin for Flipper (https://fbflipper.com/docs/setup/plugins/network/), copy your request from it as a curl, and try to perform it from your terminal or postman. If it has the same error, the problem is not in React Native.
I am developing a flutter application with network activities. To get data, I am connecting to a REST API which is pretty fast. You can see the postman performance of an API call below.
Then, when I execute the same API in flutter, this is what I got.
If you see, the secret is that postman cached the DNS Lookup, TCP Handshake and SSL Handshake. But in flutter the "Connection established" time is high, which I believe it does not do any caching.
Below is my code, connecting to network.
LoadingScreen2Controller
class LoadingScreen2Controller {
Future<void> initProcess(BuildContext context) async {
final FirebaseAuthService firebaseAuthService =
Provider.of<FirebaseAuthService>(context, listen: false);
final RoleService roleService =
Provider.of<RoleService>(context, listen: false);
fireauth.User? user = firebaseAuthService.getUser();
if (user == null) {
Navigator.of(context).pushNamedAndRemoveUntil(
'/login-select', (Route<dynamic> route) => false);
} else {
String authToken = await firebaseAuthService.getUser()!.getIdToken();
await roleService.getAllRoles(authToken); // PAY ATTENTION HERE!
Navigator.of(context)
.pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false);
}
}
}
RoleService
class RoleService with ChangeNotifier {
NavLinks _navLinks = NavLinks();
late List<Role> _roles;
/// Return roles
List<Role> returnRoles() {
return _roles;
}
/// Get all Roles
Future<void> getAllRoles(String authToken) async {
try {
var data = await http.get(
Uri.parse(_navLinks.getAllRoles()),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
);
var jsonData =
convert.json.decode(data.body).cast<Map<String, dynamic>>();
_roles = jsonData.map<Role>((json) => new Role.fromJson(json)).toList();
print(_roles);
} catch (error) {
print(error);
throw error;
}
}
}
All of my API calls are from the same URI. This "no caching" behavior is a problem for me because if I make more network calls in a row, it will take more time. For an example, if I make like 5 network calls it will take 7 seconds so on.
How can I cache the DNS Lookup, TCP Handshake and SSL Handshake in flutter?
When i send a normal get request to my deployed nodejs webserver it tell me there is no cookies sent in the header while i ve already write it in the headers of my api service
my service
constructor(private http: HttpClient, private global: Global) { }
verifAuth() {
return new Promise((resolve, reject) => {
this.http.get(`${this.global.url}verif`, this.global.header)
.toPromise()
.then(res => { resolve(res); })
.catch(err => { reject(err); console.log('err', err) });
});
my declared default header
#Injectable({
providedIn: 'root'
})
export class Global {
url: String; header;
constructor() {
this.url = "https://fmm.etudiant.xyz/";
this.header = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'cookies': `xt=${appSetting.getString('xt')}`,
}),
withCredentials: true
};
}
}
and when i debug the request i found this
request header
i expect that the token must be parsed and get the result of user but the backend server tell that there is no cookies while it work fine with an other website
Sending Cookie in Http request is not yet supported, there is a open feature request at Github.
I ran into a bug whenever I run my React Native app on an Android device (physical and emulator). Yet, no problem at all on iOS. These functions are supposed to scan the database table for user handles and return an object if the handle already exists.
This is what the error looks like:
TypeError: Cannot read property 'handle' of null
at exports.handler (/var/task/index.js:7:36)
I'm using React Native, AWS Lambda, and EXPO.
This code lives within dbfunctions.js on the front end.
export async function scanHandles(){
return new Promise((resolve, reject) => {
let { auth } = store.getState()
let reqBody = {
userId: auth.user.username,
handle: auth.handle_update,
}
let path = '/u/scan-handle'
let myInit = {
headers: { 'Content-Type': 'application/json' },
body: reqBody,
}
console.log('myInit', myInit)
console.log('handle', auth.handle_update)
API.get(apiName, path, myInit)
.then((resp) => {
// if false, then handle does not exist
// if true, then handle already exists
resolve(resp)
})
.catch((error) => {
console.warn('Scan Handle', error)
reject(error)
})
})
}
Console logging auth.handle_update does print out the expected string. myInit also prints out the expected object.
On the back end, I'm using this for my scan:
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient({ region: "us-west-1" });
exports.handler = (event, context, callback) => {
let e = JSON.parse(event.body);
var params = {
TableName: event.stageVariables.user,
FilterExpression: "handle = :handle",
ExpressionAttributeValues: { ":handle": e.handle }
};
docClient.scan(params, function(err, data) {
if (err) {
console.log("ERROR:", err);
let response = {
statusCode: err.statusCode,
headers: {},
body: JSON.stringify(err)
};
callback(response);
}
if (data.Count >= 1) {
// if user name exists
// call back handle exists response
let handleExistsResponse = {
statusCode: 200,
body: JSON.stringify({ Success: true })
};
callback(null, handleExistsResponse);
} else {
let response = {
statusCode: 200,
body: JSON.stringify({ Success: false })
};
callback(null, response);
}
});
};
Any idea as to why this would work on iOS and not Android?
EDIT:
Upon further testing, let e = JSON.parse(event.body) is returning null. So I console logged event and got a big ol object. Within this object, I found body and it's still null. So the body object isn't being passed it properly. Still confused about it working on iOS and not Android.
Did it!
Okay so API.get doesn't like body's being passed in. Instead, it wants a query parameter. So the lambda params looks like:
var params = {
TableName: event.stageVariables.user,
FilterExpression: "handle = :handle",
ExpressionAttributeValues: {
":handle": event["queryStringParameters"]["handle"]
}
};
And the front end function is:
export async function scanHandles(){
return new Promise((resolve, reject) => {
let { auth } = store.getState()
let handle = auth.handle_update
let path = `/u/scan-handle?handle=${handle}`
let myInit = {
headers: { 'Content-Type': 'application/json' },
}
API.get(apiName, path, myInit)
.then((resp) => {
// if false, then handle does not exist
// if true, then handle already exists
resolve(resp)
})
.catch((error) => {
console.warn('Scan Handle', error)
reject(error)
})
})
}
Works on both iOS and Android. Wonder why it wasn't working before?
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!