I am working with websockets on nuxt.js proyect, and i need to keep the connection opened and unique (singleton), but i could not achieve it.
I created a custom plugin where i listen for ws events, and openned the connection (checking if it doesnt exists yet -but sometimes it creates several ws instances-), and im calling this plugin from a middleware, which is using in several layouts.
The problem is:
Sometimes the connection does not establish (on page reload i.e.). How can i make sure it always starts on user login and keep openned till logout?
How do i keep just one instance of my plugin?
This is the plugin file i wrote:
export default ({ store, $axios, app }, inject) => {
class EasyWebsocket {
roomNotifications = ''
isOpen() {
return this.roomNotifications && this.roomNotifications.readyState === 1
}
initRoomNotifications(token) {
// if already open do nothing
if (this.isOpen()) {
return
}
console.log('inicializando notifications websocket')
this.roomNotifications = new WebSocket(
`wss://api/ws/chat/?t=${token}`
)
const self = this
this.roomNotifications.onmessage = async function(event) {
// CAPTURING THE WEBSOCKET NOTIFICATION
const { data } = event
const { event_type, room } = JSON.parse(data)
// TODO FRANCO: VER EL CONTADOR DE UNREADMESSAGES
const count = store.state.dashboard.chat.unreadMessagesCount + 1
store.commit('dashboard/chat/SET_UNREAD_MESSAGES_COUNT', count)
switch (event_type) {
case 'ROOM_UPDATED':
console.log('new message', room) // no borrar
await self.newRoomNotification(room)
break
case 'NEW_INVITATION':
console.log('created room', room) // no borrar
await self.newInvitation(room)
break
case 'NEW_MESSAGES':
console.log('new messages room', room) // no borrar
await self.newRoomNotification(room)
if (
store.getters['dashboard/chat/isActive']({ conversation: room })
)
store.commit('dashboard/chat/ADD_MESSAGE', room.last_message)
break
default:
break
}
}
this.roomNotifications.onopen = function(event) {
console.log('opening NOTIFICATION WEBSOCKET --------------', event) // no borrar
}
this.roomNotifications.onclose = function(event) {
}
this.roomNotifications.onerror = function(event) {
}
}
newInvitation(room) {
const invitations = store.getters['dashboard/chat/invitations']
const newInvitations = [room, ...invitations]
store.commit('dashboard/chat/SET_INVITATIONS', newInvitations)
}
async newRoomNotification(room) {
const { name } = room
// GET ALL CONVERSATIONS
const conversations = store.getters['dashboard/chat/visibleConversations']
// FIND CONVERSATION INDEX
const conversationIndex = conversations.findIndex(
(conv) => conv.name === name
)
const newConversations = [...conversations]
if (conversationIndex === -1) {
const conversation = await $axios.$get(
`v2/chat/rooms/${name}/?archived=false`
)
newConversations.unshift(conversation)
} else {
newConversations[conversationIndex] = {
...newConversations[conversationIndex],
...room
}
}
store.commit('dashboard/chat/SET_CONVERSATIONS', newConversations)
}
}
inject('easyWebsocket', EasyWebsocket)
}
Then in the middleware file:
export default async function({ store, $auth, $easyWebsocket }) {
if (!$auth.$state.loggedIn) return
// calling just on client side (websocket doesnt work on ss)
if (process.client) {
$easyWebsocket.prototype.initRoomNotifications(
//userdata
)
}
try {
await store.dispatch('dashboard/chat/getUnreadMessagesCount')
} catch (error) {}
}
Related
I am using Nest.Js and Socket.io on a server and React with Next in order to make a chat app.
But the problem is when i try to get some messages from the server i need to reconnect (If I connect for the first time handlers do not emit anything). And I think the problem is in the server (because I tried it also in Postman and the same problem is there).
Here is the code
async handleConnection(socket: Socket) {
this.server.once('connection', async (socket) => {
const token = socket.handshake.auth.token;
if (!token) {
socket.disconnect();
return;
}
const user = await this.authService.verifyAndReturnUser(token);
if (!user) {
console.log('USER IS NOT VALID');
socket.disconnect();
return;
}
// Set a userId in socket data
socket.data.userId = user.id;
// Get the rooms (chats) of the user
const userChats = await this.chatService.getUserChats(user.id);
//Emitting the rooms (chats)
socket.emit('getChats', userChats);
socket.on('joinRoom', async (data: { user: string; item?: string }) => {
// This code does not work on first connect (even the client commits)
const forwardedId = Number(data.user);
const forwardedItemId = Number(data.item);
const isForwardedNaN = Number.isNaN(forwardedId);
const isItemNan = Number.isNaN(forwardedItemId);
if (forwardedId == user.id) {
console.log('disconnect ID IS THE SAME');
socket.disconnect();
return;
}
if (!forwardedId || isForwardedNaN) {
console.log(forwardedId);
socket.disconnect();
return;
}
socket.data.forwardedId = forwardedId;
// Get forwarded info info
const getUser = await this.userService.getProfile(forwardedId);
// Get a time info
if (!getUser) {
socket.disconnect();
return;
}
const getCurrentRoom = await this.chatService.getCurrentRoom(
socket.data.forwardedId,
user.id,
);
if (getCurrentRoom) {
if (!getUser) {
socket.disconnect();
return;
}
}
socket.data.room = getCurrentRoom;
// Disconnect from all previous rooms
socket.rooms.forEach(async (room) => {
if (room) {
await socket.leave(room);
}
});
await socket.join(String(getCurrentRoom));
console.log(forwardedItemId, isItemNan);
// Set item to room
if (data.item) {
if (isItemNan) {
socket.disconnect();
return;
}
const room = await this.chatService.setItemToRoom(
getCurrentRoom,
forwardedId,
forwardedItemId,
);
// If there is no room updated disconnect
if (!room) {
socket.disconnect();
return;
}
socket.emit('getItem', room.item);
}
// Set message to seen when second user connected to socket
await this.chatService.markSeen(getCurrentRoom, user.id);
const roomMessages = await this.chatService.getRoomMessages(
getCurrentRoom,
);
// Get the user count in room
this.clientSize = (
await this.server.of('/').in(String(getCurrentRoom)).allSockets()
).size;
// Get the previous chat messages
this.server
.in(String(getCurrentRoom))
.emit('getRoomMessages', roomMessages);
// Get info about forwarded user in a room
socket.emit('getUser', getUser);
});
});
}
Im trying to build chat application using sockets and everything is working except when im trying to add new session I beilieve it is stacking the reference but I don't know what I'm missing
here is my code
const [sessions, setSessions] = useState([]);
const userSocketRef = useRef(null);
useEffect(() => {
async function getUser() {
const user = await authStorage.getUser();
const URL = "ws://192.168.1.176:3001/" + "users";
if (userSocketRef.current === null) {
userSocketRef.current = io(URL, {
auth: { user: user.uuid },
transports: ["polling", "websocket"],
});
userSocketRef.current.on("disconnect", () => {
console.log("disconnected");
});
userSocketRef.current.on("connect", () => {
console.log("connected");
});
userSocketRef.current.onAny((event, ...args) => {
console.log("event");
});
userSocketRef.current.on("connect_error", (err) => {
console.log("connect_error");
});
}
}
console.log("current sessions after handle: "+Object.keys(sessions))
getUser();
if (userSocketRef.current !== null ) {
userSocketRef.current.on(
"private message",
(message, sessionUuid) => {
console.log("private message");
handleUpdateSession(message, sessionUuid);
}
);
userSocketRef.current.on("new session", async (session) => {
console.log(Object.keys(sessions));
console.log(Object.keys(session));
await handleNewSession(session);
});
}
}, [sessions]);
useEffect(() => {
async function getSessions() {
const user = await authStorage.getUser();
const ret = await getUserSessions(user?.uuid);
setSessions(ret.data.reverse());
}
getSessions();
}, []);
const handleNewSession = async (newSession) => {
console.log("current sessions: " + Object.keys(sessions));
console.log("new session: " + Object.keys(newSession));
setSessions([newSession, ...sessions]);
};
const handleUpdateSession = (message, sessionUuid) => {
try {
console.log(sessionUuid, Object.keys(sessions));
const temp = sessions;
const session = temp.find((s) => s.uuid === sessionUuid);
session.messages.push(message);
const filteredSessions = temp.filter((s) => s.uuid !== sessionUuid);
setSessions([session, ...filteredSessions]);
} catch (error) {
console.log(error);
}
};
now when i try to open new session it works great and I can send messages but when the user tries to send message it duplicates so many times and it gets errors because they are the same key I tried to debug and here is my conclusion
LOG current sessions after handle:
LOG current sessions after handle:
LOG connected
LOG event
LOG []
LOG ["uuid", "createdAt", "updatedAt", "expirationDate", "name", "device", "messages"]
LOG current sessions:
LOG new session: uuid,createdAt,updatedAt,expirationDate,name,device,messages
LOG current sessions after handle: 0
LOG event
LOG private message
LOG a0d35995-8d79-433a-aab9-1d911d20e756 []
LOG [TypeError: undefined is not an object (evaluating 'session.messages')]
LOG private message
LOG a0d35995-8d79-433a-aab9-1d911d20e756 ["0"]
LOG current sessions after handle: 0
as you can see the useEffect loads twice I don't know why then i trigger new session and then session set to the state then I try to send message from the sender you notice that the session.messages is undefined then it finds the session
I don't know why does it stack like this
thanks
I want to add notifications to an application I've developed.
Unfortunately, Deno has removed the ws package.(https://deno.land/std#0.110.0/ws/mod.ts)
That's why I'm using the websocket inside the denon itself. Since it doesn't have many functions, I have to add some things myself.
For example, sending all messages to open clients.
What I want to do is when the pdf is created, a (data, message) comes from the socket and update the notifications on the page according to the incoming data.
I keep all open clients in a Map. and when the pdf is created, I return this Map and send it to all sockets (data, message).
However, this works for one time.
server conf...
import {
path,
paths,
ctid,
} from "../deps.ts";
const users = new Map();
const sockets = new Map()
const userArr = [];
export const startNotif = (socket,req) => {
const claims = req.get("claims");
const org = req.get("org");
claims.org = org;
console.log("connected")
users.set(claims.sub, {"username":claims.sub,"socket":socket})
users.forEach((user)=>{
if(userArr.length === 0){
userArr.push(user)
}
else if(userArr.every((w)=> w.username !== user.username) )
userArr.push(user)
})
sockets.set(org, userArr)
function broadcastMessage(message) {
sockets.get(org).map((u)=>{
console.log(u.socket.readyState)
u.socket.send(message)
})
}
if (socket.readyState === 3) {
sockets.delete(uid)
return
}
const init = (msg) => {
socket.send(
JSON.stringify({
status: "creating",
})
);
};
const ondata = async (msg) => {
const upfilepath = path.join(paths.work, `CT_${msg.sid}_report.pdf`);
try {
const s=await Deno.readTextFile(upfilepath);
if(s){
socket.send(
JSON.stringify({
status: "end",
})
);
} else {
socket.send(
JSON.stringify({
status: "creating",
})
);
}
} catch(e) {
if(e instanceof Deno.errors.NotFound)
console.error('file does not exists');
}
};
const end = () => {
try {
const endTime = Date.now()
const msg = "Your PDF has been created"
const id = ctid(12) // random id create
broadcastMessage(
JSON.stringify({
id: id,
date: endTime,
status: "done",
message: msg,
read: 'negative',
action: 'pdf'
})
);
} catch (e) {
console.log(400, "Cannot send.", e);
}
}
socket.onmessage = async (e) => {
const cmd = JSON.parse(e.data);
if(cmd.bid === 'start'){
await init(cmd)
}
if(!cmd.bid && cmd.sid){
await ondata(cmd)
}
if(cmd.bid === 'end'){
await end();
}
}
socket.onerror = (e) => {
console.log(e);
};
}
client conf...
export const webSocketHandler = (request) =>
new Promise((res, rej) => {
let url;
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
url = `http://localhost:8080/api/notifications/ws`.replace('http', 'ws');
} else {
url = `${window.location.origin}/api/notifications/ws`.replace('http', 'ws');
}
const token = JSON.parse(sessionStorage.getItem('token'));
const orgname = localStorage.getItem('orgname');
const protocol = `${token}_org_${orgname}`;
const socket = new WebSocket(url, protocol);
const response = Object.create({});
socket.onopen = function () {
socket.send(
JSON.stringify({
bid: 'start',
})
);
};
socket.onmessage = function (event) {
response.data = JSON.parse(event.data);
if (response.data.status === 'creating') {
socket.send(
JSON.stringify({
sid: request.sid,
})
);
} else if (response.data.status === 'end') {
socket.send(
JSON.stringify({
bid: 'end',
})
);
} else if (response.data.status === 'done') {
try {
res(response);
} catch (err) {
rej(err);
}
}
};
socket.onclose = function (event) {
response.state = event.returnValue;
};
socket.onerror = function (error) {
rej(error);
};
});
onclick function of button I use in component...
const donwloadReport = async (type) => {
const query = `?sid=${sid}&reportType=${type}`;
const fileName = `CT_${sid}_report.${type}`;
try {
type === 'pdf' && setLoading(true);
const response = await getScanReportAction(query);
const request = {
sid,
};
webSocketHandler(request)
.then((data) => {
console.log(data);
dispatch({
type: 'update',
data: {
id: data.data.id,
date: data.data.date,
message: data.data.message,
action: data.data.action,
read: data.data.read,
},
});
})
.catch((err) => {
console.log(err);
});
if (type === 'html') {
downloadText(response.data, fileName);
} else {
const blobUrl = await readStream(response.data);
setLoading(false);
downloadURL(blobUrl, fileName);
}
} catch (err) {
displayMessage(err.message);
}
};
Everything works perfectly the first time. When I press the download button for the pdf, the socket works, then a data is returned and I update the notification count with the context I applied according to this data.
Later I realized that this works in a single tab. When I open a new client in the side tab, my notification count does not increase. For this, I wanted to keep all sockets in Map and return them all and send a message to each socket separately. But in this case, when I press the download button for the second time, no data comes from the socket.
Actually, I think that I should do the socket initialization process on the client in the context. When you do this, it starts the socket 2 times in a meaningless way.
In summary, consider an application with organizations and users belonging to those organizations. If the clients of A, B, C users belonging to X organization are open at the same time and user A pressed a pdf download button, I want A, B, C users to be notified when the pdf is downloaded.
I would be very grateful if someone could show me a way around this issue.
Have you looked at the BroadcastChannel API? Maybe that could solve your issue. See for example:
Deno specific: https://medium.com/deno-the-complete-reference/broadcast-channel-in-deno-f76a0b8893f5
Web/Browser API: https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
I am making a react app and I need to make it so that the user can log in through the Tron Link browser extension. Now it turned out only to connect the application to the wallet, but now I need to install a new state after the connection. But after the execution of the extension call function, the state changes immediately, without waiting for the connection in the extension to complete.
How can I track if the connection is completed in the extension, and if completed, then change the state?
const getAccount = async () => {
if (window.tronWeb !== undefined) {
if (window.tronWeb.defaultAddress.base58) {
try {
setInstallFlag(true);
setlogInFlag(true);
setAddress(window.tronWeb.defaultAddress.base58); //TR9o7SXc9KqPrAxuBVb1gHMCykybxTK3GR
let instance = await window.tronWeb
.contract()
.at("TAx8Jq65YhvXc5saxFsqfLzKEwbQ1EdK64");
setContract(instance);
const account = await window.tronWeb.trx.getAccount(
window.tronWeb.defaultAddress.base58
);
if (account.assetV2) {
const temBal = account.assetV2.find(
(asset) => asset.key === "1002357"
);
if (temBal) {
setAccountBal(temBal.value / 100);
} else {
setAccountBal(0);
}
} else {
setAccountBal(0);
}
let bal = await instance
.balanceOf(window.tronWeb.defaultAddress.base58)
.call();
setBalance(bal.toNumber() / 100);
let prof = await instance
.rewardscheck(window.tronWeb.defaultAddress.base58)
.call();
setProfit(prof.toNumber() / 100);
let clamt = await instance.checkClaim().call();
setClaim(clamt.toNumber() / 100);
} catch (error) {
const msg = error.message ? error.message : error;
}
} else {
setInstallFlag(true);
setlogInFlag(false);
}
} else {
setInstallFlag(false);
setlogInFlag(false);
}
};
const login = () => {
getAccount();
setState('logged');
}
When a new user is adding to the users collection the html does'nt re-render although I can see it in the websocket.
websocket message
publication.js:
Meteor.publish('users.name.by-game', function(code) {
check(code, String);
this.autorun(function() {
const game = Game.findOne({ code });
return Meteor.users.find(
{ _id: { $in: (game && game.getPlayersId()) || [] } },
{ fields: { 'services.gitlab.username': 1 } },
);
});
});
subscribe line:
export default createContainer(({ code }) => {
const imagesHandle = Meteor.subscribe('images.all');
const usersHandle = Meteor.subscribe('users.name.by-game', code);
const gameHandle = Meteor.subscribe('games.get-by-code', code);
const loading = !imagesHandle.ready() || !gameHandle.ready() ||
!usersHandle.ready();
const game = Game.findOne();
return { loading, game };
}, GameRouterContainer);
You do not need this.autorun(function() { inside the publish function.
Also, if the problem is about rendering, the code you pasted won't let us help you.