Network error: Failed to fetch on ionic react - node.js

it's the first time that i post on forums,
I really needs your help.
I'm stuck with a problem, I have an Ionic/React application, a Node.js application and a graphQL/Apollo API,
when i'm calling the graphql API from my browser it's all working fine but when i'm building the app with capacitor and running it on my Android device, I get "Network error: Failed to fetch".
Here is my client.ts code where i'm setting up my ApolloClient
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
class RequestUtils {
clientApollo: any;
constructor() {
const httpLink = createHttpLink({
uri: 'http://192.168.1.86:4000/graphql'
});
const authLink = setContext((req, { headers }) => ({
headers: {
...headers
}
}));
this.clientApollo = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
}
}
const requestUtils = new RequestUtils();
const client: ApolloClient<NormalizedCacheObject> = requestUtils.clientApollo;
export default client;
and here is my graphql.ts
import resolvers from "../resolvers/index";
import { ApolloServer, AuthenticationError } from "apollo-server-express";
import { getModels } from "../models/index";
import schemas from "../schemas/index";
import * as express from "express";
import * as cors from "cors";
import { connectToMongo } from "./config";
import { info, success, warning, error } from "./logger";
export class query {
public app: any;
constructor() {
const models = getModels();
this.app = express();
this.app.use(cors());
try {
info("Starting connection to graphql");
const server = new ApolloServer({
typeDefs: schemas,
resolvers,
context: async ({ req }) => {
if (req) {
return {
models: models
};
}
}
});
this.app = express();
server.applyMiddleware({ app: this.app, path: "/graphql" });
success(`Connected to graphql`);
} catch (error) {
warning(error);
}
this.app.listen(4000, () => {
connectToMongo();
});
}
}
I think that it's a problem with something like cors or ip adress but i don't find any solution to my problem.
I hope someone can help me !
EDIT:
I tried to run my node server on another computer and cal the graphql API from my main computer with my ionic react react webapp. The origin is in fact different but there is no error, all works perfectly. But with my builded app on my android device, always same error.
So, I think it's not cors, maybe it's something with Capacitor/cordova, or something like this.
At first, i thought android app wasn't allowed to connect to network, I checked and it's connected, but I'm not sure.
If someone could help me, it would be very sympathic , I'm really stuck with this, my app is useless if i can't connect to server en database XD

Had exactly the same problem.
It looks like the cause is that Android forbids HTTP requests by default.
The following fix worked for me:
To allow http requests, I added android:usesCleartextTraffic="true" to my app's AndroidManifest.xml
Solution post

I "solved" my problem by launching my app with " ionic capacitor run android -l --external "
I don't what will happens if the app is in production but at least I can progress in my dev.
I'll see later what happens ¯_(ツ)_/¯

Related

Inversify - Jest - call API endpoint to test auth guard

Iam using Inversify & inversify-express-utils for building an Express Server. I am new to testing(Jest).
I need to call API instead of calling the controller class so that I can test the AuthGuard and RolesGuard. I tried using supertest library. Iam facing some issues. Im pretty sure that I have a wrong test setup.
user.controller.spec.ts:
import 'reflect-metadata';
import { mock } from 'jest-mock-extended';
import request from 'supertest';
import { MailService } from '../../../shared/mail/mail. Service';
import { UserController } from '../controller/users.controller';
import { UserService } from '../service/users.service';
import { ValidationException } from '../../../core/exception';
import { createUserFixture, updateUserFixture, userFixture } from '../fixtures/userFixture';
import { bootstrap } from '../../../main';
const userServiceMock = mock<UserService>();
const mailServiceMock = mock<MailService>();
describe('Users Controller', () => {
let app: any;
let sut: UserController;
sut = new UserController(userServiceMock, mailServiceMock);
beforeEach(async () => {
app = await bootstrap();
});
test('should throw auth error', async () => {
const req = await request(app).get('/user');
console.log(req);
});
});
main.ts
import 'reflect-metadata';
import cors from 'cors';
import express, { NextFunction, Response, Request } from 'express';
import helmet from 'helmet';
import { InversifyExpressServer } from 'inversify-express-utils';
import { container } from './core/inversify/inversify.config';
import { Sql } from './database/sql';
import { GlobalErrorConfig } from './core/server';
export const bootstrap = () => new Promise((resolve) => {
/// /Connect to SQL Server
new Sql().connect();
/// Start Server
const server = new InversifyExpressServer(container);
server.setConfig((app) => {
app.use(express.json());
app.use(cors());
app.use(helmet());
});
/// Global Error Config
GlobalErrorConfig(server);
/// Build Server
const app = server.build();
app.listen(process.env.PORT || 5000, () => {
console.log(
`Server is running on http://localhost:${process.env.PORT || 5000}`,
);
resolve(app);
});
});
bootstrap();
While trying to start the test, I get these following error:
The error:
Jest trying to warn delayed logs.
Same controller name error(app working normally on npm start).
Jest warning a file import after Jest environment torn down.
I have no clue what went wrong. I would really appreciate if anyone points out the issue or suggesting me the proper Jest API testing setup. Thanks in advance.

Websockets not connecting with postman using nestjs

I am trying to make a connection with websockets. If I need to connect to the postman I need to include app.useWebSocketAdapter(new WsAdapter(app)); to make it work. But once I include this line it stop connecting with my react native code. When I remove this line it starts connecting with react native code and not with postman. How can I make it work with both react native client side and postman.
Below is my gateway code
import { Logger } from '#nestjs/common';
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '#nestjs/websockets';
import { Server, Socket } from 'socket.io';
#WebSocketGateway()
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
#WebSocketServer()
server!: Server;
private logger: Logger = new Logger('AppGateway');
#SubscribeMessage('msgToServer')
handleMessage(client: Socket, payload: string): void {
this.server.emit('msgToClient', payload);
}
afterInit(server: Server) {
this.logger.log('Init');
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
}
}
main.ts
import { AppModule } from 'app.module';
import { WsAdapter } from '#nestjs/platform-ws';
async function setupApplication(): Promise<INestApplication> {
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(port);
return app;
}
In the above main.ts file. If I remove app.useWebSocketAdapter(new WsAdapter(app)); then I am able to connect with my react native client code but then not with postman.
react native code
import React, {useEffect} from 'react';
import {Button, SafeAreaView} from 'react-native';
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
export default function App() {
const receiveMessage = () => {
socket.on('msgToClient', msg => {
console.log('msg', msg);
});
};
const sendMessage = () => {
socket.emit('msgToServer','test message');
};
useEffect(() => {
receiveMessage()
}, []);
return (
<SafeAreaView>
<Button title="Message" onPress={sendMessage} />
</SafeAreaView>
);
}
Postman uses the ws: protocol, which is what the WsAdapter is for. By default Nest uses socket.io as the de facto WebSocket adapter which does not connect via ws diectly, but via the socket.io engine. Your react code is using socket.io so your server code should use socket.io too. This does mean that you won't be able to use postman to test your websockets though, unless you keep swapping back and forth.
Postman now has support to Socket.io servers (it's in beta):
Step by step from the provided link:
In the left-hand sidebar, click New.
Select WebSocket Request.
Within the new tab’s header, click the drop-down that says Raw, and select Socket.IO instead.
Type the address of your Socket.IO server into the URL bar.
Click Connect.
As you do not provide a specific namespace on the #WebSocketGateway() decorator and assuming that you are using the default NestJS port 3000, the step 4 address would be:
http://localhost:3000
Additionally, if you are running NestJS in WSL, you might want to see this answer

Jest tests did not exit (one second) after the test run has completed using a simple Express application

I have a simple Express application that exposes a RESTful API. It uses Knex and Objection to access the database, and Jest / Supertest for testing the API.
I have a test that starts the server, fetches the available data from a given route and asserts the received values. Everything works fine, except that Jest never exits after executing this test.
This is my test:
import { app } from "../../src/application.js";
import { describe, expect, test } from "#jest/globals";
import request from "supertest";
describe("Customer Handler", () => {
test("should retrieve all existing customer(s)", async () => {
const expected = [
// ...real values here; omitted for brevity
];
const response = await request(app).get(`/api/customers`);
expect(response.statusCode).toStrictEqual(200);
expect(response.headers["content-type"]).toContain("application/json");
expect(response.body).toStrictEqual(expected);
});
});
The application.js file looks very much like a usual Express setup/configuration file:
import { CustomerHandler } from "./handler/customer.handler.js";
import connection from "../knexfile.js";
import express from "express";
import Knex from "knex";
import { Model } from "objection";
const app = express();
Model.knex(Knex(connection[process.env.NODE_ENV]));
// ...removed helmet and some other config for brevity
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api/customers", CustomerHandler);
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
errors: {
error: err,
message: err.message,
},
});
next();
});
export { app };
I've tried --detectOpenHandles, but nothing else is printed in the console, so I can't see any hints about what the issue might be — which I suspect it's Knex / Objection because I'm using SQLite, so probably the connection to the database is not getting closed.
In the meantime I'm using --forceExit, but I would like to figure it out why is Jest not able to exit.
OK, looks like I was on the right track for this one.
Refactoring application.js to export the Knex object (after it's configured), and calling knex.destroy() after the test passes is the solution for this setup.
// application.js
...
const knex = Knex(connection[process.env.NODE_ENV]);
Model.knex(knex);
...
export { app, knex };
...then in the test(s), make sure you import knex and call destroy():
// customer.handler.test.js
import { app, knex } from "../../src/application.js";
import { afterAll, describe, expect, test } from "#jest/globals";
import request from "supertest";
describe("Customer Handler", () => {
afterAll(async () => {
await knex.destroy();
});
test("should retrieve all existing customer(s)", async () => {
const expected = [ /* ...real values here */ ];
...
expect(response.body).toStrictEqual(expected);
});
});

EventPattern handler is not being triggered outside controllers

I'm trying to setup a NestJS Hybrid Application that listens to a Redis service for events and triggers a handler in my code. I've defined a publisher that publishes events via ClientProxy.emit() as shown in the documentation.
EventPublisher.ts
#Injectable()
export class EventPublisher implements IEventPublisher {
constructor(#Inject('redisClient') private client: ClientProxy) {}
async publish<T extends IEvent = IEvent>(event: T) {
await this.client.emit('a', JSON.stringify({event})).toPromise();
}
}
I'm injecting my ClientProxy via my module class
const redisClient = ClientsModule.register([
{ name: 'redisClient', transport: Transport.REDIS, options: {
url: 'redis://localhost:6379'}
}
])
#Module({
imports: [
redisClient
],
... Other setups
})
export class FooModule {};
To listen to incoming events I created a microservice that uses redis as its transport.
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
const redisMicroservice = app.connectMicroservice<RedisOptions>({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379'
}
})
await app.startAllMicroservicesAsync();
await app.listen(process.env.PORT || 3000);
}
This setup works fine as I am recieving the send data in my terminal when I subscirbe to 'a' using the redis-cli. I've tried defining eventhandlers on multiple classes in my code, but none of them are triggered when an event is emitted to Redis unless I create a handler that is located in a controller.
controller.ts
#EventPattern('a')
doSomething() {
console.log('success')
}
In the documentation I can't find any mention of eventhandlers having to be in a controller, but that seems to be the only location that triggers the handlers right now. Would anyone know what could be causing this? Any help is really appreciated.
I came to the same realisation yesterday. I tried to look for an explanation in the documentation, but as often with NestJS, I found none. #Decorators make it hard to investigate as they hide their logic.
It seems indeed that #EventPattern() will only be triggered if placed inside a #Controller(), but I have yet to understand why.

Axios in a Node.js container on Kubernetes is returning "ECONNREFUSED 127.0.0.1:30561"?

Full error message: connect ECONNREFUSED 127.0.0.1:30561 at TCPConnectWrap.afterConnect
The axios request is running in a Node.js environment (Next.js), which is where the error occurs, strangely the axios request works perfectly fine when it is being run in the browser.
My component (running in Node.js) that calls axios:
import axios from 'axios'
import Router from 'next/router'
import React, { Component } from 'react'
import { initializeStore } from '~/reducers'
import { authenticate } from '~/actions/auth'
import { getCookieByName } from '~/helpers/cookie'
const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
function getOrCreateStore(initialState) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return initializeStore(initialState)
}
// Create store if unavailable on the client and set it on the window object
if (!window[__NEXT_REDUX_STORE__]) {
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
}
return window[__NEXT_REDUX_STORE__]
}
export default App => {
return class AppWithRedux extends Component {
static async getInitialProps(appContext) {
const reduxStore = getOrCreateStore()
appContext.ctx.reduxStore = reduxStore
let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
}
const JWT = (isServer ? getCookieByName('JWT', appContext.ctx.req.headers.cookie) : getCookieByName('JWT', document.cookie))
const pathname = appContext.ctx.pathname
//set axios baseURL
axios.defaults.baseURL = (isServer ? `${appContext.ctx.req.headers['x-forwarded-proto']}://${appContext.ctx.req.headers.host}` : window.location.origin)
//if user has a JWT
if(JWT){
axios.defaults.headers.common['Authorization'] = `Bearer ${JWT}`
//get user from API layer
reduxStore.dispatch(authenticate())
}
return {
...appProps,
initialReduxState: reduxStore.getState()
}
}
constructor(props) {
super(props)
this.reduxStore = getOrCreateStore(props.initialReduxState)
}
render() {
return <App {...this.props} reduxStore={this.reduxStore} />
}
}
}
Specifically reduxStore.dispatch(authenticate())
And my actual axios call (using redux thunk), looking at the authenticate method:
import axios from 'axios'
import { setCookieByName } from '~/helpers/cookie'
const BASE_URL = '/api/auth'
export const TYPE_REGISTER = 'TYPE_REGISTER'
export const TYPE_AUTHENTICATE = 'TYPE_AUTHENTICATE'
export const register = values => (dispatch) => {
return axios.post(`${BASE_URL}/register`, values)
.then(function({data: {token, user}}){
setCookieByName('JWT', token, 365)
dispatch({
type: TYPE_REGISTER,
payload: user
})
})
}
export const authenticate = () => (dispatch) => {
return axios.post(`${BASE_URL}/me`)
.then(function({data: {user}}){
dispatch({
type: TYPE_AUTHENTICATE,
payload: user
})
})
.catch(function(err){
console.log(err)
dispatch({
type: TYPE_AUTHENTICATE,
payload: {}
})
})
}
I'm running my local Kubernetes cluster using Docker for Mac, and my Ingress controller is being accessed on http://kludge.info:30561. My domain is mapped from 127.0.0.1 kludge.info locally to allow the Ingress controller to hit the container. My theory is that when I send a request to http://kludge.info:30561/api/auth/me for example, the docker container running the Node.js app thinks it is a request to localhost (inside the container), and results in a connection error. Please note that the Node.js app inside the container is running on http://localhost:8080. Basically I'm running localhost on my machine, and localhost inside the Node instance. How could I send a request outside to http://kludge.info:30561/ where the Ingress controller is running.
I've also configured the baseURLin axios, but it does not solve the problem. My ingress controller has a path /api that will point to a PHP instance, so I need my Node.js axios call to hit that inside it's container. Any help would be much appreciated.
When I ran my K8 cluster on Minikube I did not have this problem, however minikube does provide you with the IP of the VM, while Docker for Desktop uses localhost directly on your machine.
If I understand you correctly I see that you open a socket on lcoalhost(127.0.0.1) so it is only accessible locally. If you want it to be accessible from outside you need to bind it to 0.0.0.0 meaning "all interfaces".
Listening on 0.0.0.0 means listening from anywhere with network access to this computer. For example, from this very computer, from local network or from the Internet. And listening on 127.0.0.1 means from this computer only.
Please let me know if that helped. Or if I have misunderstood you.

Resources