Send message from service to other components (React Native) - react-native

I'm thinking about the best way how to implement rabbitmq or mqtt sending messages from callback(queue.on) to chat component and last dialogs component, where I will update dialogs and chats. I heard about eventemitter, but I don't know best way,try to use eventemitter or make own handlers. I am very new in react.
MessageBrokerService:
import {Connection, Exchange, Queue} from "react-native-rabbitmq";
import uuid from 'react-native-uuid';
export default class RabbitMQService {
startEventSubscribe = (exchange) => {
exchangeName = exchange;
connection.connect();
}
}
const config = {
host: '',
port: 5671,
username: '',
password: '',
virtualhost: '/',
ttl: 10000,
ssl: true,
};
let connection = new Connection(config);
let queue;
let exchangeName;
connection.on('connected', event => {
queue = new Queue(
connection,
{
name: uuid.v4(),
durable: true,
},
{
// queueDeclare args here like x-message-ttl
},
);
let exchange = new Exchange(connection, {
name: exchangeName,
});
queue.bind(exchange, '');
// Receive messages
queue.on('message', message => {
console.log(message);
**I want from here send messages to chat component and last dialogs from here**
queue.basicAck(message.delivery_tag);
});
});
connection.on('error', event => {
console.log('fail');
console.log(event);
});
const publishMessage = message => {
let routing_key = 'message-exchange';
let properties = {
//header authorization jwt
};
let exchangeSendMessages = new Exchange(connection, {
name: 'message-exchange',
type: 'direct',
durable: true,
});
exchangeSendMessages.publish(message, routing_key, properties);
};
Last Dialogs screen:
const LastDialogsScreen = () => {
**Want here to receive messages here from callback in MessageBrokerService**
return ()
}
Chat screen:
const ChatScreen = () => {
**Want here to receive messages in here too from callback in MessageBrokerService**
return ()
}

Related

Nestjs rabbitmq publisher, consumer and hhtp API in one application

I need to know if it's possible to have all these three in single application. I have seen many examples where there are two different project one for RabbitMQ publisher and one for subscriber/consumer.
I just need to know if there's a possibility to implement something like that. I failed to find any example consisting both in one app.
I have tried to implement something similar but it did not work.
folder structure :
src
--main.ts
--app.service.ts
--app.resolver.ts
--app.module.ts
main.ts file
async function bootstrap() {
let RUNPORT = process.env.PORT ? process.env.PORT : 3000;
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: [`amqp://guest:guest#localhost:5672`],
queue: 'email-subscribers',
queueOptions: {
durable: true,
},
noAck: false,
// Get one by one
prefetchCount: 1,
},
});
app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();
app.module.ts file
providers: [
AppService,
AppResolver,
{
provide: 'GREETING_SERVICE',
useFactory: (configService: ConfigService) => {
const user = configService.get('RABBITMQ_USER');
const password = configService.get('RABBITMQ_PASSWORD');
const host = configService.get('RABBITMQ_HOST');
const queueName = configService.get('RABBITMQ_QUEUE_NAME');
return ClientProxyFactory.create({
transport: Transport.RMQ,
options: {
urls: [`amqp://${user}:${password}#${host}`],
queue: queueName,
queueOptions: {
durable: true,
},
},
});
},
inject: [ConfigService],
},
],
app.resolver.ts
#Resolver()
export class AppResolver {
constructor(
private readonly appService: AppService,
#Inject('GREETING_SERVICE') private client: ClientProxy,
) {}
#Public()
#Query(() => String)
async getHello() {
// this.client.emit('new_message', { text: 'Myu data' });
this.client.send('new_message', 'boom baby').subscribe();
return await this.appService.getHello();
}
#EventPattern('new_message')
async getDataPublish(data) {
console.log(data);
// return this.appService.publishEvent(data);
}

Getting pc.iceConnectionState Checking, failed in pc.oniceconnectionstatechange event in webRTC

I'm using webrtc for video calling in react native app.
If I call to someone else and receivers receives call then I get stream of receiver but there is a problem at receiver side.
Receiver gets remotestream but it shows blank view.
import AsyncStorage from '#react-native-async-storage/async-storage';
import {
RTCIceCandidate,
RTCPeerConnection,
RTCSessionDescription,
} from 'react-native-webrtc';
import io from '../scripts/socket.io';
const PC_CONFIG = {
iceServers: [
{ url: 'stun:motac85002'},
],
};
export const pc = new RTCPeerConnection(PC_CONFIG);
// Signaling methods
export const onData = data => {
handleSignalingData(data.data);
};
const ENDPOINT = 'http://52.52.75.250:3000/';
const socket = io(ENDPOINT);
// const PeerConnection = () => {
const sendData = async data => {
const roomId = await AsyncStorage.getItem('roomId');
const userId = parseInt(await AsyncStorage.getItem('user_id'));
socket.emit('data', roomId, userId, data);
};
export const createPeerConnection = async(stream, setUsers) => {
try {
pc.onicecandidate = onIceCandidate;
const userId = parseInt(await AsyncStorage.getItem('user_id'));
pc.onaddstream = e => {
setUsers(e.stream);
};
pc.addStream(stream)
pc.oniceconnectionstatechange = function () {
// console.log('ICE state: ', pc);
console.log('iceConnectionState', pc.iceConnectionState);
if (pc.iceConnectionState === "failed" ||
pc.iceConnectionState === "disconnected" ||
pc.iceConnectionState === "closed") {
console.log('iceConnectionState restart', userId);
// console.log('ICE state: ', pc);
// Handle the failure
pc.restartIce();
}
};
console.log('PeerConnection created', userId);
// sendOffer();
} catch (error) {
console.error('PeerConnection failed: ', error);
}
};
export const callSomeone = () => {
pc.createOffer({}).then(setAndSendLocalDescription, error => {
console.error('Send offer failed: ', error);
});
};
const setAndSendLocalDescription = sessionDescription => {
pc.setLocalDescription(sessionDescription);
sendData(sessionDescription);
};
const onIceCandidate = event => {
if (event.candidate) {
sendData({
type: 'candidate',
candidate: event.candidate,
});
}
};
export const disconnectPeer = () =>{
pc.close();
}
const sendAnswer = () => {
pc.createAnswer().then(setAndSendLocalDescription, error => {
console.error('Send answer failed: ', error);
});
};
export const handleSignalingData = data => {
switch (data.type) {
case 'offer':
pc.setRemoteDescription(new RTCSessionDescription(data));
sendAnswer();
break;
case 'answer':
pc.setRemoteDescription(new RTCSessionDescription(data));
break;
case 'candidate':
pc.addIceCandidate(new RTCIceCandidate(data.candidate));
break;
}
};
// }
// export default PeerConnection
Can anyone please tell me why at receiver side video stream is not displaying?
Also there is a remoteStream issue in motorola device. Why is this happening?
This statement:
const PC_CONFIG = {
iceServers: [
{ url: 'stun:motac85002'},
],
};
Has two potential issues:
First, the parameter for the iceServers object should be urls, not url. (Although it wouldn't surprise me if the browsers accepted either).
Second, as I mentioned in comments to your question, the STUN address itself looks to be a local address instead of an Internet address. That might explain why you aren't seeing any srflx or UDP candidates in the SDP. And as such, that might explain connectivity issues.
So instead of the above, could you try this:
const PC_CONFIG= {iceServers: [{urls: "stun:stun.stunprotocol.org"}]};

Keep client connected to WebSocket in react native and express server

I have a react native application where i have two users using the app (customer and restaurant)
So on checkout I connect the customer to websocket on the express server and once the order is placed i send a message to the restaurant which is supposed to be connected to websocket all time.
However, sometimes the restaurant is disconnected somehow, so I am trying to keep the restaurant connected, and if disconnected then reconnect again automatically.
In react native restaurant side implementation i have the following code :
this is useWebSocketLite hook to handle connection, send, receive messages and retry connection to server when closed:
function useWebSocketLite({ socketUrl, retry: defaultRetry = 3, retryInterval = 1000 }) {
const [data, setData] = useState();
const [send, setSend] = useState(() => () => undefined);
const [retry, setRetry] = useState(defaultRetry);
const [readyState, setReadyState] = useState(false);
useEffect(() => {
const ws = new WebSocket(socketUrl);
ws.onopen = () => {
setReadyState(true);
setSend(() => {
return (data) => {
try {
const d = JSON.stringify(data);
ws.send(d);
return true;
} catch (err) {
return false;
}
};
});
ws.onmessage = (event) => {
const msg = formatMessage(event.data);
setData({ message: msg, timestamp: getTimestamp() });
};
};
ws.onclose = () => {
setReadyState(false);
if (retry > 0) {
setTimeout(() => {
setRetry((retry) => retry - 1);
}, retryInterval);
}
};
return () => {
ws.close();
};
}, [retry]);
return { send, data, readyState };
}
So based on this, every-time the connection is closed, the connection will retry again.
Besides, when a restaurant launches the app the following code will be implemented:
const ws = useWebSocketLite({
socketUrl: `wss://${url}/id=${user.user_id}&role=restaurant`
});
This useEffect to establish the connection:
useEffect(() => {
if (ws.readyState === true) {
setConnectionOpen(true);
}
}, [ws.readyState]);
and this useEffect to handle incoming messages
useEffect(() => {
if (ws.data) {
const message = ws.data;
//dispatch...
}
}, [ws.data]);
Express server implementation:
This is the code where i handle socket connections and messages in express server:
var webSockets = {}
function setupWebSocket(server) {
server.on('connection', (socket, req) => {
if (req) {
var clientId = req.url
let regexReplace = /[\[\]/]/g
let regex = /([^=#&]+)=([^?&#]*)/g,
params = {},
match;
while ((match = regex.exec(clientId))) {
params[decodeURIComponent(match[1]).replace(regexReplace, '')] = decodeURIComponent(match[2])
}
if (params.role === 'restaurant') {
webSockets[params.id] = socket
}
}
socket.on('message', data => {
let sData = JSON.parse(JSON.parse(data))
let {id, data} = sData.data
sendToClient(id, 'order', data)
})
socket.on('error', (err) => {
console.log(err)
})
socket.on('close', (code, req) => {
var clientId = req.url
let regexReplace = /[\[\]/]/g
let regex = /([^=#&]+)=([^?&#]*)/g,
params = {},
match;
while ((match = regex.exec(clientId))) {
params[decodeURIComponent(match[1]).replace(regexReplace, '')] = decodeURIComponent(match[2])
}
if (params.role === 'restaurant') {
delete webSockets[clientId]
console.log(`${webSockets[clientId]} disconnected with code ${code} !`);
}
});
});
// sends a message to a specific client
const sendToClient = (clientId, type, data = {}) => {
const payload = { type, data }
const messageToSend = JSON.stringify({ error: false, message: payload })
if (webSockets[clientId]) {
webSockets[clientId].send(messageToSend)
console.log(`${clientId} client notified with this order`)
} else {
console.log(`${clientId} websocket client is not connected.`)
}
}
}
So most of the time I get 13 websocket client is not connected. which means the restaurant has already been deleted from the webSockets object and its connection already closed.
Apologise for long question and hope someone can help me regarding this.
First of all, you should know that this is not a good practice of websockets, where you are forcing the client (the restaurant) to be connected.
Whatever, at the current state of your code, there is an illogical behavior: at the end of the useEffect of your “useWebSocketLite” function, you are closing the socket connection:
return () => {
ws.close();
};
Knowing that the useEffect hook is called twice: after the first render of the component, and then after every change of the dependencies (the “retry” state in your case); Your code can be ridden like so: everytime the “retry” state changes, we will close the socket! So for me that is why you got the client disconnected.

WebSocket onmessage is not triggered when onsend is called

I am developing a stateless typescript backend with WebSocket. I created a SocketMiddleware as a middleware to my redux state based on dev.io tutorial. The first socket.send() message from onopen works fine. However, I can't trigger the subsequent onmessage using SEND_MSG dispatch.
The backend shows that it receives a log but it is not received by the clients. I am sure that the connection_id is already set correctly
const socketMiddleware = () => {
let socket = null;
const onOpen = (store) => (event) => {
store.dispatch({ type: "WS_CONNECTED" });
};
const onClose = (store) => () => {
store.dispatch({ type: "WS_DISCONNECTED" });
};
const onMessage = (store) => (message) => {
console.log("message received #middleware, ", message.data);
const payload = JSON.parse(message.data);
switch (payload.action) {
case "get_connection_id":
const { connectionId } = payload;
store.dispatch({
type: "UPDATE_MY_CONNECTION_ID",
payload: { myConnectionId: connectionId },
});
break;
case "join_room_socket":
const { match_id, players, connectionIdArr } = payload;
if (match_id) {
store.dispatch({
type: "UPDATE_ROOM",
payload: {
players: players,
match_id: match_id,
connectionIdArr: connectionIdArr,
},
});
}
break;
case "broadcast_action":
const { move } = body;
store.dispatch({
type: "UPDATE_GAME_STATE",
payload: { move: move },
});
}
};
return (store) => (next) => (action) => {
switch (action.type) {
case "WS_CONNECT":
if (socket !== null) socket.close();
socket = new WebSocket(process.env.WSS_ENDPOINT);
socket.onmessage = onMessage(store);
socket.onclose = onClose(store);
socket.onopen = onOpen(store);
break;
case "WS_CONNECTED":
console.log("WebSocket client is connected");
socket.send(JSON.stringify({ action: "get_connection_id" }));
case "WS_DISCONNECTED":
console.log("WebSocket client is disconnected");
case "SEND_MSG":
console.log("sending a message", action);
socket.send(JSON.stringify({ ...action.payload }));
default:
console.log("the next action:", action);
return next(action);
}
};
};
export default socketMiddleware();
my redux store
...
const persistConfig = {
key: "root",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, reducer);
const store = createStore(
persistedReducer,
compose(applyMiddleware(reduxThunk, wsMiddleware))
);
my backend side:
joinRoomSocket: (data) => {
const body = JSON.parse(data.body);
body.connectionIdArr.map((connectionId) => {
const endpoint = `${data.requestContext.domainName}/${data.requestContext.stage}`;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: "2018-11-29",
endpoint,
});
const params = {
ConnectionId: connectionId,
Data: JSON.stringify(body),
};
return apigwManagementApi.postToConnection(params).promise();
});
},
Which onmessage are you referring to server or client? If you are referring to onMessage callback of client. For this you need to send something from the server using websocket.send('text message').
For client to receive message on onmessage event, server needs to send data.
Here is the flow:
Client ws.onsend('abc') ----------------> Server ws.onmessage(data)
Server ws.onsend('abc') ----------------> Client ws.onmessage(data)```

Apollo subscriptions JWT authentication

I am using Robin Wieruch's fullstack boilerplate but it is missing authentication for subscriptions. It uses JWT token for sessions and it is working fine for http but for ws auth is completely missing.
I need to pass user trough context for subscriptions as well, I need session info in subscriptions resolver to be able to decide weather I should fire subscription or not.
I did search Apollo docs, I saw I should use onConnect: (connectionParams, webSocket, context) function, but there is no fully functional fullstack example, I am not sure how to pass JWT from client to be able to get it in webSocket object.
Here is what I have so far:
Server:
import express from 'express';
import {
ApolloServer,
AuthenticationError,
} from 'apollo-server-express';
const app = express();
app.use(cors());
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, process.env.SECRET);
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
const server = new ApolloServer({
introspection: true,
typeDefs: schema,
resolvers,
subscriptions: {
onConnect: (connectionParams, webSocket, context) => {
console.log(webSocket);
},
},
context: async ({ req, connection }) => {
// subscriptions
if (connection) {
return {
// how to pass me here as well?
models,
};
}
// mutations and queries
if (req) {
const me = await getMe(req);
return {
models,
me,
secret: process.env.SECRET,
};
}
},
});
server.applyMiddleware({ app, path: '/graphql' });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
const isTest = !!process.env.TEST_DATABASE_URL;
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || 8000;
httpServer.listen({ port }, () => {
console.log(`Apollo Server on http://localhost:${port}/graphql`);
});
Client:
const httpLink = createUploadLink({
uri: 'http://localhost:8000/graphql',
fetch: customFetch,
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:8000/graphql`,
options: {
reconnect: true,
},
});
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition' && operation === 'subscription'
);
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => {
const token = localStorage.getItem('token');
if (token) {
headers = { ...headers, 'x-token': token };
}
return { headers };
});
return forward(operation);
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
console.log('GraphQL error', message);
if (message === 'UNAUTHENTICATED') {
signOut(client);
}
});
}
if (networkError) {
console.log('Network error', networkError);
if (networkError.statusCode === 401) {
signOut(client);
}
}
});
const link = ApolloLink.from([authLink, errorLink, terminatingLink]);
const cache = new InMemoryCache();
const client = new ApolloClient({
link,
cache,
resolvers,
typeDefs,
});
You need to use connectionParams to set the JWT from the client-side. Below is the code snippet using the angular framework:
const WS_URI = `wss://${environment.HOST}:${environment.PORT}${
environment.WS_PATH
}`;
const wsClient = subscriptionService.getWSClient(WS_URI, {
lazy: true,
// When connectionParams is a function, it gets evaluated before each connection.
connectionParams: () => {
return {
token: `Bearer ${authService.getJwt()}`
};
},
reconnect: true,
reconnectionAttempts: 5,
connectionCallback: (error: Error[]) => {
if (error) {
console.log(error);
}
console.log('connectionCallback');
},
inactivityTimeout: 1000
});
const wsLink = new WebSocketLink(wsClient);
In your server-side, you are correct, using onConnect event handler to handle the JWT. E.g.
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
server-side: https://github.com/mrdulin/apollo-graphql-tutorial/blob/master/src/subscriptions/server.ts#L72
client-side: https://github.com/mrdulin/angular-apollo-starter/blob/master/src/app/graphql/graphql.module.ts#L38