agora - onUserJoined - how to differentiate specific broadcaster/host - agora.io

onUserJoined provides UID which is then used to set up remote video.
in live stream feature when there are two hosts streaming in one channel and if another audience joins later then how can that audience tell which uid is for which host.
Note that onUserJoined does call in random order.
https://docs.agora.io/en/All/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_channel_event_handler.html#a65fd197a39824219aedc2cba81296e82
Use case:
2 hosts already streaming in one channel. 1 of the host is original host and another is a co-host(got invited into the channel)
an audience joins the channel. onUserJoined callback is triggered twice and given the remote UID. That audience now needs to know who is the original host and related his UID and who is the co-host and his related UID.

You basically Answered your self
LIVE_BROADCASTING profile: This callback notifies the app when the host joins the channel. If other hosts are already in the channel, the SDK also reports to the app on the existing hosts. We recommend having at most 17 hosts in a channel.

I finally found a solution for that issue by informing all users that will enter the channel after the Orignal Host by the uid of the orignal host
and you can do this by using sendStreamMessage() function
First joinChannelSuccess triggers after original host login
which will return uid of the original host - and we will save it in a variable.
Then, use sendStreamMessage() to send this uid for all new users
and that by using this function which will be called when userJoined triggers
check this code
int _orignalHost = 'the uid';
_addListener() {
_engine.setEventHandler(RtcEngineEventHandler(
**joinChannelSuccess:** (channel, uid, elapsed) {
setState(() {
isJoined = true;
});
},
**userJoined:** (uid, elapsed) {
remoteUids.add(uid);
setState(() {});
},
// this trriger when you recive the mesage
**streamMessage:** (int uid, int streamId, Uint8List data) {
// save it in _orignalHost variable for the audince
},
streamMessageError:
(int uid, int streamId, ErrorCode error, int missed, int cached) {
},
));
}
Future<void> _onNewUserJoin() async {
try {
var streamId = await _engine
.createDataStreamWithConfig(DataStreamConfig(false, false));
if (streamId != null) {
await _engine.sendStreamMessage(
streamId, Uint8List.fromList(utf8.encode(_orignalHost)));
}
_controller.clear();
} catch (e) {
}
}
you can check full code for StreamMessage:
https://github.com/AgoraIO/Agora-Flutter-SDK/blob/master/example/lib/examples/advanced/stream_message/stream_message.dart
Also, You can check this for more info (anoter solution)
https://docs.agora.io/en/Real-time-Messaging/faq/audience_event?platform=Android

Related

Show private messages list of multiple users logged in at the same time

I've created a private messaging app where users login and can view their private messages. The problem that I am having is that is only shows the message list of one logged in user at a time. So let's say User A is logged in, it will show the chat list of user A. But then User B logs in, then both User A and User B will see the chat list of User B.
This is my server side call to fetch chats by user id:
Im using express for the backend
io.on('connection', socket => {
socket.on('findAllChatsByUserId', (userId) => {
socket.userId = userId
socket.join(socket.userId)
ChatModel.aggregate([{$match: {$or:[{senderId: userId},{receiver: userId}]}}, {$group: {_id: '$chatId', 'data': {$last: '$$ROOT'}}}]).exec(function(error, data) {
if (error) {
return error
} else {
data.sort(function (a, b) {
return b.data.date - a.data.date;
});
io.to(socket.userId).emit('findAllChatsByUserId', data);
}
})
})
});
And on the client side I do:
I am using VueJs on the FE
mounted () {
this.loading = true
this.socket.emit('findAllChatsByUserId', this.getUserId) // this calls the socket to get the chats for the given user Id
this.loading = false
},
I tried creating rooms by userId to make sure that only the data for a given user ID is passed in but it seems like only one user can use the socket at a time. I thought the rooms would solve this issue for me. Do I have to create a separate socket for each user? If so, how do I do that? I've followed the socket.io private messaging tutorial but they use 2 users talking to each other to explain the problem.
So I ended up solving this by doing:
io.to(socket.id).emit('findAllChatsByUserId', data);
instead of:
io.to(socket.userId).emit('findAllChatsByUserId', data);
So you use the "to" attribute to make sure the data you're sending is going to a particular socket, and you can find your specific socket by just calling socket.id (you don't have to set this, it gets set on its own. And the data will get emitted to whomever is on that specific socket.

how to kick a user in agora.io?

im trying to make it possible for a room host to kick a user, im using the rest api to kick a user, when i send a request with the following body:
public CompletableFuture<Boolean> actionOnUser(String action, Long channelId, String userId) {
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
Map body = new HashMap<>();
body.put("appid", agoraConfig.getAppId());
body.put("uid", userId);
body.put("time", 0);
body.put("cname", channelId.toString());
body.put("ip", "");
String[] privileges = { action };
body.put("privileges", privileges);
Mono<String> agoraResponse = webClient().post().uri("/dev/v1/kicking-rule").bodyValue(body).retrieve()
.bodyToMono(String.class).doOnSuccess(response -> {
logger.info("kicking-rule response status:" + response);
completableFuture.complete(true);
}).doOnError(onError -> {
logger.info("kicking rule failed" + onError);
completableFuture.complete(false);
});
agoraResponse.subscribe();
return completableFuture;
}
i get a 200 OK response with an id of the kicking rule, but the user isn't being kicked, how can i fix this?
Edit: apparently agora doesnt accept strings as uid, had to switch to int which is unfortunate because i preferred to use UUID, but this fixes the issue
agora doesnt accept strings as uid, had to switch to int which is unfortunate because i preferred to use UUID, but this fixes the issue
For implementing kick user from call functionality you can use Agora RTM SDK.
For ref:- https://www.agora.io/en/blog/muting-and-unmuting-a-remote-user-in-a-video-call-web/
Also, you can use your custom WebSocket.

How to set max concurrent logins per user in apache SshServer

I need to limit the concurrent sessions allowed per user in an apache SshServer. I found two references to this functionality, but they seem to be obsolete.
Here's the original patch back in 2010:
https://issues.apache.org/jira/browse/SSHD-95
I also found this reference to its usage:
http://apache-mina.10907.n7.nabble.com/How-to-set-max-count-connections-in-sshd-service-td44764.html
Which refers to a SshServer.setProperty() method.
I'm using sshd-core 2.4.0, and this method is absent from SshServer, I can't see any obvious replacement, and I can't find any documentation on what has happened to it or how I'm supposed to do this now.
I still see the MAX_CONCURRENT_SESSIONS key in ServerFactoryManager, so I assume the functionality is still in there somewhere, but I can't find where I need to set it.
Here's what the setup of the server looks like (it's for an SFTP server, but that shouldn't matter for the problem at ahnd, I thnk):
private val server = SshServer.setUpDefaultServer().apply {
val sftpSubsystemFactory = SftpSubsystemFactory().apply {
addSftpEventListener(sftpEventListener)
}
port = sftpPort
host = "localhost"
keyPairProvider = when {
sftpKeyname.isEmpty() -> throw IllegalStateException("No key name for SFTP, aborting!")
sftpKeyname == "NO_RSA" -> {
log.warn("Explicitly using NO_RSA, sftp encryption is insecure!")
SimpleGeneratorHostKeyProvider(File("host.ser").toPath())
}
else -> KeyPairProvider.wrap(loadKeyPair(sftpKeyname))
}
setPasswordAuthenticator { username, password, _ ->
// current evil hack to prevent users from opening more than one session
if (activeSessions.any { it.username == username }) {
log.warn("User attempted multiple concurrent sessions!")
throw IllegalUserStateException("User already has a session!")
} else {
log.debug("new session for user $username")
// throws AuthenticationException
authenticationService.checkCredentials(username, password)
true
}
}
subsystemFactories = listOf(sftpSubsystemFactory)
fileSystemFactory = YellowSftpFilesystemFactory(ftpHome)
start()
log.info("SFTP server started on port $port")
}
(From my comment) you can set the property directly:
server.apply {
properties[ServerFactoryManager.MAX_CONCURRENT_SESSIONS] = 50L
}

How to securely store phone numbers in Twilio Functions?

I am using Twilio Functions. I wonder if the phone numbers stored in the Function's code are secure?
I am using code similar to that found here: https://support.twilio.com/hc/en-us/articles/223180548-How-Can-I-Stop-Receiving-or-Block-Incoming-Phone-Calls-#blacklistNumbers
Basically, it rejects numbers that on a blacklist of your choosing. The blacklist is in the Function's code itself.
Perhaps this is already secure. Forgive my misunderstanding.
The aforementioned code:
exports.handler = function(context, event, callback) {
// Listing all the blocked phone numbers, at the moment "+1(212)555-1234" and "+1(702)555-6789"
let blacklist = event.blacklist || [ "+12125551234", "+17025556789" ];
let twiml = new Twilio.twiml.VoiceResponse();
let blocked = true;
if (blacklist.length > 0) {
if (blacklist.indexOf(event.From) === -1) {
blocked = false;
}
}
if (blocked) {
twiml.reject();
}
else {
// if the caller's number is not blocked, redirecting to another TwiML which includes instructions for what to do
twiml.redirect("https://demo.twilio.com/docs/voice.xml");
}
callback(null, twiml);
};
Heyooo. Twilio Developer Evangelist here. 👋
I don't think you have to worry about the security of these numbers. But what you could do it to add the blacklist to the function configuration instead. This way you wouldn't have to change the function code whenever you want to add a new number to the blacklist.
These values below will be available in the context object that is passed into your function.
exports.handler = async function(context, event, callback) {
console.log(context.BLACKLIST); // 1212...
}

Limit Google Sign-In to .edu accounts in Meteor

I'm trying to limit my Google + Sign-In Button to only allow #something.edu accounts to sign in. How would I go about doing this. This is my code so far:
Template.googleLogin.events({
'click #gLogin': function(event) {
Meteor.loginWithGoogle({}, function(err){
if (err) {
throw new Meteor.Error("Google login didn't work!");
}
else {
Router.go('/home')
}
});
}
})
Template.primaryLayout.events({
'click #gLogout': function(event) {
Meteor.logout(function(err){
if (err) {
throw new Meteor.Error("Hmm looks like your logout failed. ");
}
else {
Router.go('/')
}
})
}
})
You can accomplish this using Accounts.config (in the root directory, so it runs on both the client and server)
Accounts.config({ restrictCreationByEmailDomain: 'something.edu' })
If you need something more custom, you can replace something.edu with a method if you need to fine grain your requirement, i.e for any .edu domain:
Accounts.config({ restrictCreationByEmailDomain: function(address) {
return new RegExp('\\.edu$', 'i')).test(address)
}
});
The accounts package allows configuring account creation domain through:
Accounts.config({
restrictCreationByEmailDomain: 'something.edu'
})
But this has some limitations in case of google:
This is only client side and only allows for the login form to get properly styled to represent the domain's logo etc. But it can be very easily overcome by crafting the google oauth signin url by hand
In case you need to configure extra options like allowing multiple domains or a domain and some outside users (perhaps third party contractors or support from a software company etc) this does not work. In case of accounts-google, the package checks if restrictCreationByEmailDomain is a String and if it is instead a function, it just discards it.
Therefore, to be able to properly and securely utilize such functionality, you need to use the official Accounts.validateNewUser callback:
Accounts.validateNewUser(function(newUser) {
var newUserEmail = newUser.services.google.email;
if (!newUserEmail) throw new Meteor.Error(403,'You need a valid email address to sign up.');
if (!checkEmailAgainstAllowed(newUserEmail)) throw new Meteor.Error(403,'You need an accepted organization email address to sign up.');
return true;
});
var checkEmailAgainstAllowed = function(email) {
var allowedDomains = ['something.edu'];
var allowedEmails = ['someone#example.com'];
var domain = email.replace(/.*#/,'').toLowerCase();
return _.contains(allowedEmails, email) || _.contains(allowedDomains, domain);
};
If you want to be extra cautious, you can implement the same for the Accounts.validateLoginAttempt and Accounts.onCreateUser callbacks as well.