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)```
Related
I have a tcp socket client connection and server in my app. My main goal is taking data from server and send it to client connection. It works great while my app is running but if i get app to background it just sends one data and sends other datas after opening app again. I tried expo-task-manager but it just sends first data and doesnt sends after it as before. I was using my function inside useEffect in a context component. My function with task manager is below.
const BACKGROUND_CLIENT_TASK = "background-client-task";
TaskManager.defineTask(BACKGROUND_CLIENT_TASK, async () => {
const now = Date.now();
console.log(
`Got background fetch call at date: ${new Date(now).toISOString()}`
);
const mainFunction = async () => {
const server = TcpSocket.createServer(function (socket) {
socket.on("data", (takenData) => {
const jsonStringData = String.fromCharCode(...takenData);
const data = JSON.parse(jsonStringData);
const clientOptions = {
port: 1500,
host: "localhost",
};
const dataToClientArray = [
`DataProcess1${data}`,
`DataProcess2${data}`,
`DataProcess3${data}`,
];
dataToClientArray.forEach((dataToClient, index) => {
setTimeout(() => {
const client = TcpSocket.createConnection(
clientOptions,
() => {
// Write on the socket
client.write(`${dataToClient}`, "hex");
console.log("client started");
console.log(dataToClient);
// Close socket
client.destroy();
}
);
client.on("data", (data) => {
console.log("Received: ", data.toString());
});
client.on("error", (error) => {
console.log("Error: ", error);
});
client.on("close", () => {
console.log("Connection closed");
});
}, 300 * index);
});
});
socket.on("error", (error) => {
console.log("An error ocurred with client socket ", error);
});
socket.on("close", (error) => {
console.log("Closed connection with ", socket.address());
});
}).listen({ port: 1754, host: "0.0.0.0" });
server.on("error", (error) => {
console.log("An error ocurred with the server", error);
});
server.on("close", () => {
console.log("Server closed connection");
});
};
mainFunction();
// Be sure to return the successful result type!
return BackgroundFetch.BackgroundFetchResult.NewData;
});
BTW It doesnt start if i dont add the mainFunction in useEffect
The code that i register my defined Task. (result returns undefined)
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_CLIENT_TASK, {
minimumInterval: 5, // seconds
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
const registerFunction = async () => {
const result = await registerBackgroundFetchAsync();
console.log(result);
console.log("resultup");
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_CLIENT_TASK
);
setStatus(status);
setIsRegistered(isRegistered);
};
registerFunction();
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"}]};
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.
I am new coding in Javascript.
I am creating a WebRTC connection between my iPhone and my browser.
The connection works but my code only send one candidate and I don't know if I am doing anything wrong. I would appreciate any comment or support.
Thanks
const createPeerConnection = (signaling) => {
const peerConnection = new RTCPeerConnection({
iceServers: [],
});
const offerOptions = {
offerToReceiveVideo: true, offerToReceiveAudio: true
};
peerConnection.createOffer(offerOptions);
createAndSendOffer(signaling, peerConnection);
peerConnection.onicecandidate = (iceEvent) => {
if (iceEvent && iceEvent.candidate) {
signaling.send(JSON.stringify({
type: MESSAGE_TYPE.IceCandidate,
payload: iceEvent.candidate,
}));
}
};
peerConnection.onconnectionstatechange = (state ) => {
console.log(peerConnection.connectionState);
};
return peerConnection;
};
const createAndSendOffer = async (signaling, peerConnection) => {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signaling.send(JSON.stringify({ type: MESSAGE_TYPE.SessionDescription, payload: offer }));
};
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