I am building a chat in my app. I am using socket.io for this. When a user sends a message I send an api request to my server. The api stores the message in the database and only then emits, with a socket service to everyone in the room that there is a new message. I have a SocketService class with this method:
private async broadcast({ type, data, chatId, senderId }: { type: string; data: any; chatId: string; senderId: string }) {
const excludedSocket = await this.getUserSocket(senderId);
if (chatId && excludedSocket) {
excludedSocket.emit;
} else if (excludedSocket) {
excludedSocket.emit(type, data);
} else if (room) {
gIo.to(room).emit(type, data);
} else {
gIo.emit(type, data);
}
}
The problem I have is that getUserSocket returns a RemoteSocket object that doesn't have the broadcast or methods on it. So how can I achieve this?
private async getUserSocket(userId: string) {
const sockets = await this.getAllSockets();
const socket = sockets.find((s) => s.data.uid === userId);
return socket;
}
private async getAllSockets() {
const sockets = await this.io.fetchSockets();
return sockets
}
socket.broadcast.emit equates to a BroadcastOperator that sends to an empty Set of rooms (meaning "all rooms"), excluding the socket id "room" of the sender Socket.
The same BroadcastOperator can be created from the server instance with server.except()
const sockets = await this.io.except(socketIdString).emit('blah', x)
Related
I'm trying to make a list of connected users using key and value pairs. The key will be the connected user, when I received a message I want to emit this to the receiver and sender (to and from)
Now I'm stuck on the part where I want to add the new connected user. How do I add the user to the user to the array?
Second issue is the foreach is not a function
let connectedUsers: { [key: string]: Socket }
io.on('connection', (socket: Socket) => {
socket.on('USER_CONNECTED', (profile: Profile) => {
connectedUsers !== undefined &&
Object.keys(connectedUsers).includes(profile.userId) &&
Object.assign(connectedUsers, { [profile.userId]: socket })
console.log(connectedUsers)
})
socket.on('MESSAGE_SENT', (message: ChatMessage) => {
connectedUsers.forEach(cli => {
if (cli.key = message.to || cli.key = message.from) {
cli.emit('MESSAGE_RECIEVED', message)
}
});
})
connectedUsers is not an array but an object, so you can't use forEach to iterate it.
The good news is that you don't need it!
When connectedUsers is populated you can refactor on message function like
socket.on('MESSAGE_SENT', (message: ChatMessage) => {
connectedUsers[message.to].emit('MESSAGE_RECIEVED', message);
connectedUsers[message.from].emit('MESSAGE_RECIEVED', message);
})
To add a user to the object connectedUsers simply
socket.on('USER_CONNECTED', (profile: Profile) => {
connectedUsers = Object.assign(connectedUsers, { [profile.userId]: socket })
console.log(connectedUsers)
})
I have a server backend written in Python with Flask-SocketIO. I'm utilizing it's room feature to make private conversations. Upon a join room event the server fires the following function to let the frontend know where to send messages to specific user:
socketio.emit('room name response', {'roomName': room_name, 'recipient': recipient}, to=sid)
where sid is the private room created only for the user when connecting to a socket. Then I want to keep this information in React state in a map, like this:
function ChatWindow({ username, token }) {
const [responses, setResponses] = useState([]);
const [roomsMap, setRoomsMap] = useState(new Map());
const [currentRoom, setCurrentRoom] = useState("");
const [messageValue, setMessageValue] = useState("");
var socket = null;
useEffect(() => {
socket = socketIOClient(ENDPOINT);
});
useEffect(() => {
socket.on("global response", (data) => {
setResponses((responses) => [...responses, data]);
});
socket.on("room name response", (data) => {
console.log(`joined ${data.roomName} with ${data.recipient}`);
setCurrentRoom((currentRoom) => data.roomName);
setRoomsMap((roomsMap) => roomsMap.set(data.recipient, data.roomName));
});
return () => socket.close();
}, []);
const sendMessage = () => {
if (messageValue.length < 1) {
return;
}
socket.emit("global message", {
user_name: username,
message: messageValue,
timestamp: Date.now(),
});
setMessageValue("");
};
const joinRoom = (recipient) => {
socket.emit("join", {
token: token,
username: username,
recipient: recipient,
});
// setCurrentRoom(() => roomsMap.get(recipient));
};
const leaveRoom = (recipient) => {
socket.emit("leave", {
token: token,
username: username,
recipient: recipient,
});
const newRooms = roomsMap;
newRooms.delete(recipient);
console.log(`left room with ${recipient}`);
newRooms.forEach((val, key) => console.log(`${val}:${key}`));
setRoomsMap(newRooms);
};
const checkUser = (userToCheck) => {
if (userToCheck === username) {
return styles.chatFromUser;
} else {
return styles.chatToUser;
}
};
return (...);
}
export default ChatWindow;
Sadly, React doesnt react to the socket emitting message, even though it can be seen in network tab in developer tools. The global response works fine.
When I alter the backend function to:
socketio.emit('room name response', {'roomName': room_name, 'recipient': recipient})
React suddenly works as expected. I'm trying to understand why it happens, especially when the browser seems to see the incoming messages as stated above, so it's most likely my bad coding or some React/Javascript thing.
Thank You for any help in advance.
The problem was that socket sometimes was created multiple times, therefore, the socket that useEffect was currently listening wasn't necessarily the one in the room. So I made one, global socket to fix this and whole thing now works.
I do want to create an access token in the backend and need to pass to the front end to connect to the video chat room.
This is my back-end code
const twilioAccountSid = process.env.twilioAccountSid;
const twilioApiKey = process.env.twilioApiKey;
const twilioApiSecret = process.env.twilioApiSecret;
const room = "cool room";
app.post("/access-token", (req, res) => {
try {
console.log(
"sid",
twilioAccountSid,
"key",
twilioApiKey,
"secret",
twilioApiSecret
);
const identity = "user";
// Create Video Grant
const videoGrant = new VideoGrant({
room,
});
// Create an access token which we will sign and return to the client,
// containing the grant we just created
const token = new AccessToken(
twilioAccountSid,
twilioApiKey,
twilioApiSecret,
{ identity: identity }
);
token.addGrant(videoGrant);
// Serialize the token to a JWT string
console.log(token.toJwt());
res.status(200).json(token.toJwt());
} catch (error) {
console.warn(error);
res.sendStatus(500);
}
});
For the Twilio account SID I used my dashboard's SID which is starting from AC
For the API key I added the friendly name I gave to the API key when I created it.
API secret is that API key's secret id.
A token is crearted succefully and passed to the front-end.
This is my front-end code
const connectRoom = async () => {
try {
const token = await axios.post("http://localhost:5000/access-token");
connect(token.data, { name: roomName, video: { width: 640 } }).then(
(room) => {
console.log(`Successfully joined a Room: ${room}`);
room.on("participantConnected", (participant) => {
console.log(`A remote Participant connected: ${participant}`);
participant.tracks.forEach((publication) => {
console.log("for each");
if (publication.isSubscribed) {
const track = publication.track;
document
.getElementById("remote-media-div")
.appendChild(track.attach());
}
});
participant.on("trackSubscribed", (track) => {
document
.getElementById("remote-media-div")
.appendChild(track.attach());
});
});
},
(error) => {
console.error(`Unable to connect to Room: ${error.message}`);
}
);
} catch (error) {
console.log(error);
}
Then I get this error
Unable to connect to Room: Invalid Access Token issuer/subject
How do I solve this problem?
Any help!
Thanks in advance
You can create an API Key here (or via the Console). Note, the API Key starts with SK....
REST API: API Keys
I want to show my notifications in a channel that I create so I can fully customize my channel with my preferences. I'm using a Firebase function to send notifications (messages) from user to user:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.pushNotification = functions.firestore.document('/devices/{tokenId}/notifications/{notificationId}')
.onWrite((change, context) => {
console.log('Push notification event triggered');
const tokenId = context.params.tokenId;
const document = change.after.exists ? change.after.data() : null;
if (document == null) {
return console.log('A notification has been deleted from database');
}
const payload = {
notification: {
title: document.username,
body: document.message,
sound: "default"
},
data: {
sender: document.sender
}
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24 //24 hours
};
return admin.messaging().sendToDevice(tokenId, payload, options).then(result => {
console.log('A notification sent to device with tokenId: ', tokenId);
});
});
I have implemented my FirebaseMessagingService service:
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
showNotification(remoteMessage);
}
private void showNotification(RemoteMessage remoteMessage) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
RemoteMessage.Notification remoteNotification = remoteMessage.getNotification();
if (remoteNotification == null) return;
String title = remoteNotification.getTitle();
String message = remoteNotification.getBody();
Notification notification;
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(this, CH_MESSAGE) : new Notification.Builder(this);
notification = builder
.setContentTitle(title)
.setContentText(message)
.setCategory(CATEGORY_MESSAGE)
.build();
notificationManager.notify(0, notification);
}
And created my own notification channel at my Application class:
#Override
public void onCreate() {
super.onCreate();
createNotificationChannels();
}
private void createNotificationChannels() {
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (notificationManager == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CH_MESSAGE,
getString(R.string.messages), NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(getString(R.string.message_channel_description));
notificationManager.createNotificationChannel(notificationChannel);
}
}
I can successfully send notifications, but notifications are going to Miscellaneous channel.
I tried to remove the channel by using its channel ID with notificationManager.deleteNotificationChannel("fcm_fallback_notification_channel");, but it still recreates the channel and sends the notification there. How can I remove Miscellaneous channel permanently and handle my notifications with my own channels?
I have found the problem I am facing, my message payload contains both notification and data fields. According to this documentation my messages were not calling onMessageReceived method when app is in background. Now I'm using only data payload which calls the method when app is at both background and foreground.
const payload = {
data: {
sender: document.sender,
username: document.username,
message: document.message
}
};
And in my onMessageReceived method:
Map<String, String> remoteMap = remoteMessage.getData();
String senderUid = remoteMap.get("sender");
String senderUsername = remoteMap.get("username");
String message = remoteMap.get("message");
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(this, CH_MESSAGE) : new Notification.Builder(this);
Notification notification = builder
.setContentTitle(senderUsername)
.setContentText(message)
.setSmallIcon(R.drawable.ic_notif_message)
.build();
notificationManager.notify(0, notification);
I'm sending message to my bot using Microsoft BotConnector but they are not being logged as normal messages. For logging messages to the DB I wrote custom logger :
class CustomLogger {
/**
* Log an activity to the transcript file.
* #param activity Activity being logged.
*/
constructor() {
this.conversations = {};
}
logActivity(activity) {
if (activity) {
console.log("Log information")
}
if (!activity) {
throw new Error("Activity is required.");
}
if (activity.conversation) {
var id = activity.conversation.id;
if (id.indexOf("|" !== -1)) {
id = activity.conversation.id.replace(/\|.*/, "");
}
}
if (activity.type === "message") {
Conv.create({
text: activity.text,
conv_id: activity.conversation.id,
from_type: activity.from.role,
message_id: activity.id || activity.replyToId
}).then(() => {
console.log("logged");
});
delete this.conversations[id];
}
}
}
it works great with normal messages but it is no working with the messages that are sent to
POST /v3/conversations/{conversationId}/activities
via microsoft bot connector.
When I send message using the the bot connector it doesn't log the request via activity.
Code that I'm using to send proactive msg:
/**
* Send message to the user.
*/
function sendMessage(token, conversation, name) {
var config = {
headers: { "Authorization": "Bearer " + token }
};
var bodyParameters = {
"type": "message",
"text": name
}
axios.post(
'https://smba.trafficmanager.net/apis/v3/conversations/29:XXXXXXXXXXXXXXX/activities',
bodyParameters,
config
).then((response) => {
console.log(response)
}).catch((error) => {
console.log(error)
});
}
let name = "Hey, How was your week?";
let conversation = "29:XXXXXXXXXXXXXXX";
run(conversation, name);
Instead of using the REST API to send proactive messages to users, I would recommend using the BotFramework Adapter to continue the conversation with the user. When you send the proactive message from the adapter, the activity passes through the logger middleware and gets saved to storage. If you would like to initiate the proactive message from an Azure Function, you can set up another messaging endpoint in the index file that you call from the function. Take a look at the code snippets below.
index.js
// Listen for incoming notifications and send proactive messages to user.
server.get('/api/notify/:conversationID', async (req, res) => {
const { conversationID } = req.params;
const conversationReference = conversationReferences[conversationID];
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('proactive hello');
});
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
res.end();
});
For more details I would take a look at this Proactive Messages Sample. It is in the samples-work-in-progress branch and might change slightly, but it is a great example of how to configure your project to send a proactive message from a Restify endpoint.
Hope this helps!