Selective Deliveries

Last updated 2 months ago

Mitter provides Access Control Lists (ACLs) out of the box to let you choose whom to deliver a message to in a group channel. Let’s add a little function to our app to see this in action.

Login as different users

We created multiple users for our app through the Mitter.io Dashboard at the beginning of this guide. If you haven’t already, take some time to create 3 or more users and copy down their IDs and auth tokens.

Now, you can obviously put up a login page to your app and use a backend server as a token server to login as a user; but for the sake of simplicity, we’ll just swap out the user credentials before building our app.

Get started by defining 3 UserAuth objects inside the onCreate() method in your MyApp class like this:

Kotlin
Java
MyApp.kt
val jasonAuth = UserAuth(
userId = "8ed92f3c-0696-4513-a842-085e3cee589e",
userAuthToken = "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6IlJkZHZCNXJ5RGdPNUpvSkoiLCJ1c2VydG9rZW4iOiI5cXA1MjQyY2N1NW9lbHI3OTRzc2ltbDY3OCJ9.aonmZhZWCyIHJR6nxlNn_KSgAvdWlB4vtZgfdXbvlIvXBM5oNaUzpF3YbfAlZeyPr8_uMf8HCcoh4dVFr-lYFw"
)
val katieAuth = UserAuth(
userId = "cb02bc00-979e-4db2-8625-116178c4ad95",
userAuthToken = "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6Ilg2ZXZKZDVHSmx1SU5URWEiLCJ1c2VydG9rZW4iOiJwdnA2MGFwa3NpYjgzY21yOGI1N2g0YmhpYSJ9.hVw0h3hmtOQlx9phWrJ7zK9an9GmXv9H481OjrbIPOmE58g86EqozHLhASwl0jdiqC5KjU1_nrIK40pmNEvY9w"
)
val samAuth = UserAuth(
userId = "5cfe3da1-4467-49b8-8325-3e85cec31c5a",
userAuthToken = "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6IlhoSnV5dGw0OG5OSlBJVDYiLCJ1c2VydG9rZW4iOiIyamozaTdhNTA5Yzc3c2Vva2dscGdiZjBpNiJ9.BwPGV2kTdclHdRaIzqjR6yAascqvYE52tH2iLK2aDBaBZA841SM0gW0WdblxLYeAyyM-XKXIEHwM2K0xQwoh7w"
)
MyApp.java
UserAuth jasonAuth = new UserAuth(
"8ed92f3c-0696-4513-a842-085e3cee589e",
"eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6ImJtRlI5bWNQaDhkQnJHaWIiLCJ1c2VydG9rZW4iOiJiM2dvY2ZhZ3ZyNWlrNHJkbXJlc29wNnNlcyJ9.dlE1QOYmUJpqoh1kORm3hEI3KbBM0v8kKZQnQQwXR6TZuFiCaDQrJMlp-2dgNP1CTYCPMFYoqGctRWQ5JyNiOQ"
);
UserAuth katieAuth = new UserAuth(
"cb02bc00-979e-4db2-8625-116178c4ad95",
"eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6Ilg2ZXZKZDVHSmx1SU5URWEiLCJ1c2VydG9rZW4iOiJwdnA2MGFwa3NpYjgzY21yOGI1N2g0YmhpYSJ9.hVw0h3hmtOQlx9phWrJ7zK9an9GmXv9H481OjrbIPOmE58g86EqozHLhASwl0jdiqC5KjU1_nrIK40pmNEvY9w"
);
UserAuth samAuth = new UserAuth(
"5cfe3da1-4467-49b8-8325-3e85cec31c5a",
"eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJtaXR0ZXItaW8iLCJ1c2VyVG9rZW5JZCI6IlhoSnV5dGw0OG5OSlBJVDYiLCJ1c2VydG9rZW4iOiIyamozaTdhNTA5Yzc3c2Vva2dscGdiZjBpNiJ9.BwPGV2kTdclHdRaIzqjR6yAascqvYE52tH2iLK2aDBaBZA841SM0gW0WdblxLYeAyyM-XKXIEHwM2K0xQwoh7w"
);

Next, fire up 3 emulator instances or 3 devices, whichever you prefer, each time swapping out the userAuth parameter on the Mitter object configuration defined in your Application class. Like this:

Kotlin
Java
MyApp.kt
mitter = Mitter(
context = this,
mitterConfig = mitterConfig,
userAuth = samAuth
)
MyApp.java
mitter = new Mitter(
this,
mitterConfig,
samAuth
);

Therefore, first, you can pass userAuth as jasonAuth and run the project on one emulator instance. Then you can change the userAuth to katieAuth and then run on a different emulator instance.

This way you can get 3 different users in your 3 emulator instances, and then you can chat between the 3 in real-time.

Implement the @username mapping

For our demo, we’ll be adding a feature where a user can mention another specific user by the @username notation while typing their messages, and the message will only be visible to the mentioned user and the sender.

Before we can get started on that, we need to have a local mapping of the usernames with their actual user IDs on the platform.

Define an object class (or a class with static methods, if using Java) named UserIdProvider and add the following piece of code, changing the user IDs and names to your actual user details.

Kotlin
Java
UserIdProvider.kt
object UserIdProvider {
fun getUserId(username: String): String = when (username.toLowerCase()) {
"sam" -> "5cfe3da1-4467-49b8-8325-3e85cec31c5a"
"jason" -> "8ed92f3c-0696-4513-a842-085e3cee589e"
"katie" -> "cb02bc00-979e-4db2-8625-116178c4ad95"
else -> ""
}
}
UserIdProvider.java
public class UserIdProvider {
public static String getUserId(String username) {
switch (username.toLowerCase()) {
case "sam":
return "5cfe3da1-4467-49b8-8325-3e85cec31c5a";
case "jason":
return "8ed92f3c-0696-4513-a842-085e3cee589e";
case "katie":
return "cb02bc00-979e-4db2-8625-116178c4ad95";
default:
return "";
}
}
}

Do note that these usernames are just local names that you can use to identify a user with the @username notation.

Add ACL rules

Now that you’ve defined the mapping, you need to define some ACL rules that you’ll be passing along with your messages to make the selective delivery work.

Create another object class (or a plain class with static method, if using Java) called AclUtils and add the following piece of code:

Kotlin
Java
AclUtils.kt
object AclUtils {
fun meAndSam(senderId: String): AppliedAclList {
return AppliedAclList(
plusAppliedAcls = listOf(
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("sam")))
),
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(senderId))
)
),
minusAppliedAcls = emptyList()
)
}
fun meAndJason(senderId: String): AppliedAclList {
return AppliedAclList(
plusAppliedAcls = listOf(
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("jason")))
),
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(senderId))
)
),
minusAppliedAcls = emptyList()
)
}
fun meAndKatie(senderId: String): AppliedAclList {
return AppliedAclList(
plusAppliedAcls = listOf(
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("katie")))
),
AppliedAcl
(
ReadMessagePrivilege(),
UserIdAccessorSelector(IdUtils.of(senderId))
)
),
minusAppliedAcls = emptyList()
)
}
fun getAclListFromUsername(username: String, senderId: String) = when (username) {
"sam" -> meAndSam(senderId)
"jason" -> meAndJason(senderId)
"katie" -> meAndKatie(senderId)
else -> emptyAclList()
}
}
AclUtils.java
public class AclUtils {
public static AppliedAclList meAndSam(String senderId) {
List<AppliedAcl> plusAppliedAcls = new ArrayList<>();
List<AppliedAcl> minusAppliedAcls = new ArrayList<>();
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("sam"), User.class))
)
);
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(senderId, User.class))
)
);
return new AppliedAclList(
plusAppliedAcls,
minusAppliedAcls
);
}
public static AppliedAclList meAndJason(String senderId) {
List<AppliedAcl> plusAppliedAcls = new ArrayList<>();
List<AppliedAcl> minusAppliedAcls = new ArrayList<>();
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("jason"), User.class))
)
);
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(senderId, User.class))
)
);
return new AppliedAclList(
plusAppliedAcls,
minusAppliedAcls
);
}
public static AppliedAclList meAndKatie(String senderId) {
List<AppliedAcl> plusAppliedAcls = new ArrayList<>();
List<AppliedAcl> minusAppliedAcls = new ArrayList<>();
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(UserIdProvider.getUserId("katie"), User.class))
)
);
plusAppliedAcls.add(
new AppliedAcl(
new ReadMessagePrivilege(),
new UserIdAccessorSelector(IdUtils.of(senderId, User.class))
)
);
return new AppliedAclList(
plusAppliedAcls,
minusAppliedAcls
);
}
public static AppliedAclList getAclListFromUsername(String username, String senderId) {
switch (username) {
case "sam":
return meAndSam(senderId);
case "jason":
return meAndJason(senderId);
case "katie":
return meAndKatie(senderId);
default:
return new AppliedAclList(new ArrayList<AppliedAcl>(), new ArrayList<AppliedAcl>());
}
}
}

Here, we just define some basic ACL rules based on the chosen username. You can learn more about ACLs and how to use them over here.

For example, the method meAndSam() provides an AppliedAclList object which tells Mitter.io that the message is only for Sam and the current sender to view. Anyone else in the group will be unaware of this message.

This way you can have a private conversation between users in a group of multiple users.

Apply ACLs to your outgoing messages

The sendTextMessage() method that we used previously to send out messages, accepts another optional parameter called appliedAcls where you can pass an AppliedAclList object containing your ACL rules for the message you’re sending.

The goal here is to parse the input text and check if there’s an @ symbol present in the message and then extract the username from that message to determine our ACLs.

We’ll be using our utility methods to determine the ACLs for the username that we get from the input text.

Modify your button click event to this:

Kotlin
Java
MainActivity.kt
sendButton.setOnClickListener {
var typedInput = inputMessage?.text.toString()
var appliedAcls = emptyAclList()
if (typedInput.contains('@')) {
val username = typedInput.substring(1, typedInput.indexOf(' '))
typedInput = typedInput.substringAfter(' ')
appliedAcls = AclUtils.getAclListFromUsername(username, mitter.getUserId())
}
messaging.sendTextMessage(
channelId = channelId,
message = typedInput,
appliedAcls = appliedAcls,
onValueUpdatedCallback = object : Mitter.OnValueUpdatedCallback {
override fun onError(apiError: ApiError) {}
override fun onSuccess() {
inputMessage?.text?.clear()
}
}
)
}
MainActivity.java
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String typedInput = inputMessage.getText().toString();
AppliedAclList appliedAcls = new AppliedAclList(
new ArrayList<AppliedAcl>(),
new ArrayList<AppliedAcl>()
);
if (typedInput.contains("@")) {
String username = typedInput.substring(1, typedInput.indexOf(" "));
typedInput = typedInput.substring(typedInput.indexOf(" "));
appliedAcls = AclUtils.getAclListFromUsername(username, mitter.getUserId());
}
messaging.sendTextMessage(
channelId,
typedInput,
appliedAcls,
new Mitter.OnValueUpdatedCallback() {
@Override
public void onSuccess() {
inputMessage.getText().clear();
}
@Override
public void onError(ApiError apiError) { }
}
);
}
});

Here, we just do a basic check for the @ symbol, substring the text accordingly, and pass the message along with the applied ACLs to the sendTextMessage() method.

Now, if you run the app on all the 3 emulator instances with 3 different users, you can chat among the users and send private messages by just mentioning a user’s name prefixed with an @ symbol.

For example, if Katie would like to send a message to just Jason, she can type “@jason What’s up?”.

This will make the message visible only to Katie and Jason. Sam will be totally unaware that this conversation ever happened.

Selective delivery in action

That’s how you can use ACLs to control who sees what. Feel free to define your own use cases and play around.