Skip to content

Commit

Permalink
feat: Groups Consent
Browse files Browse the repository at this point in the history
Added allowGroups denyGroups
Added isAllowed & isDenied for groups
  • Loading branch information
Alex Risch authored and Alex Risch committed Mar 12, 2024
1 parent c6d0be1 commit b560c25
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import java.util.UUID
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import com.facebook.common.util.Hex

class ReactNativeSigner(var module: XMTPModule, override var address: String) : SigningKey {
private val continuations: MutableMap<String, Continuation<Signature>> = mutableMapOf()
Expand Down Expand Up @@ -913,6 +914,32 @@ class XMTPModule : Module() {
logV("preEnableIdentityCallbackCompleted")
preEnableIdentityCallbackDeferred?.complete(Unit)
}

AsyncFunction("allowGroups") { clientAddress: String, groupIds: List<String> ->
logV("allowGroups")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val groupDataIds = groupIds.mapNotNull { Hex.hexStringToByteArray(it) }
client.contacts.allowGroup(groupDataIds)
}

AsyncFunction("denyGroups") { clientAddress: String, groupIds: List<String> ->
logV("denyGroups")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val groupDataIds = groupIds.mapNotNull { Hex.hexStringToByteArray(it) }
client.contacts.denyGroup(groupDataIds)
}

AsyncFunction("isGroupAllowed") { clientAddress: String, groupId: String ->
logV("isGroupAllowed")
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.isGroupAllowed(Hex.hexStringToByteArray(groupId))
}

AsyncFunction("isGroupDenied") { clientAddress: String, groupId: String ->
logV("isGroupDenied")
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.isGroupDenied(Hex.hexStringToByteArray(groupId))
}
}

//
Expand Down
81 changes: 81 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,87 @@ test('can stream all group Messages from multiple clients - swapped', async () =
return true
})

test('creating a group should allow group', async () => {
const [alix, bo] = await createClients(2)

const group = await alix.conversations.newGroup([bo.address])
const consent = await alix.contacts.isGroupAllowed(group.id)

if (!consent) {
throw Error('Group should be allowed')
}

return true
})

test('can allow a group', async () => {
const [alix, bo] = await createClients(2)
const alixGroup = await alix.conversations.newGroup([bo.address])
const startConsent = await bo.contacts.isGroupAllowed(alixGroup.id)
if (startConsent) {
throw Error('Group should not be allowed')
}
await bo.contacts.allowGroups([alixGroup.id])
const isAllowed = await bo.contacts.isGroupAllowed(alixGroup.id)
if (!isAllowed) {
throw Error('Group should be allowed')
}

return true
})

test('can deny a group', async () => {
const [alix, bo] = await createClients(2)
const alixGroup = await alix.conversations.newGroup([bo.address])
const startConsent = await bo.contacts.isGroupDenied(alixGroup.id)
if (startConsent) {
throw Error('Group should be unknown')
}
await bo.contacts.denyGroups([alixGroup.id])
const isDenied = await bo.contacts.isGroupDenied(alixGroup.id)
if (!isDenied) {
throw Error('Group should be denied')
}
await bo.contacts.allowGroups([alixGroup.id])
const isAllowed = await bo.contacts.isGroupAllowed(alixGroup.id)
if (!isAllowed) {
throw Error('Group should be allowed')
}

return true
})

test('can check if group is allowed', async () => {
const [alix, bo] = await createClients(2)
const alixGroup = await alix.conversations.newGroup([bo.address])
const startConsent = await bo.contacts.isGroupAllowed(alixGroup.id)
if (startConsent) {
throw Error('Group should not be allowed by default')
}
await bo.contacts.allowGroups([alixGroup.id])
const consent = await bo.contacts.isGroupAllowed(alixGroup.id)
if (!consent) {
throw Error('Group should be allowed')
}

return true
})

test('can check if group is denied', async () => {
const [alix, bo] = await createClients(2)
const alixGroup = await alix.conversations.newGroup([bo.address])
const startConsent = await bo.contacts.isGroupDenied(alixGroup.id)
if (startConsent) {
throw Error('Group should not be denied by default')
}
await bo.contacts.denyGroups([alixGroup.id])
const consent = await bo.contacts.isGroupDenied(alixGroup.id)
if (!consent) {
throw Error('Group should be denied')
}
return true
})

// Commenting this out so it doesn't block people, but nice to have?
// test('can stream messages for a long time', async () => {
// const bo = await Client.createRandom({ env: 'local', enableAlphaMls: true })
Expand Down
38 changes: 37 additions & 1 deletion ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class XMTPModule: Module {
}

enum Error: Swift.Error {
case noClient, conversationNotFound(String), noMessage, invalidKeyBundle, invalidDigest, badPreparation(String), mlsNotEnabled(String)
case noClient, conversationNotFound(String), noMessage, invalidKeyBundle, invalidDigest, badPreparation(String), mlsNotEnabled(String), invalidString
}

public func definition() -> ModuleDefinition {
Expand Down Expand Up @@ -867,6 +867,42 @@ public class XMTPModule: Module {
self.preCreateIdentityCallbackDeferred = nil
}
}

AsyncFunction("allowGroups") { (clientAddress: String, groupIds: [String]) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
let groupDataIds = groupIds.compactMap { Data(hex: $0) }
try await client.contacts.allowGroup(groupIds: groupDataIds)
}

AsyncFunction("denyGroups") { (clientAddress: String, groupIds: [String]) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
let groupDataIds = groupIds.compactMap { Data(hex: $0) }
try await client.contacts.denyGroup(groupIds: groupDataIds)
}

AsyncFunction("isGroupAllowed") { (clientAddress: String, groupId: String) -> Bool in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
guard let groupDataId = Data(hex: groupId) else {
throw Error.invalidString
}
return try await client.contacts.isGroupAllowed(groupId: groupDataId)
}

AsyncFunction("isGroupDenied") { (clientAddress: String, groupId: String) -> Bool in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.invalidString
}
guard let groupDataId = Data(hex: groupId) else {
throw Error.invalidString
}
return try await client.contacts.isGroupDenied(groupId: groupDataId)
}
}

//
Expand Down
28 changes: 28 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,34 @@ export async function isGroupAdmin(
return XMTPModule.isGroupAdmin(clientAddress, id)
}

export async function allowGroups(
clientAddress: string,
groupIds: string[]
): Promise<void> {
return XMTPModule.allowGroups(clientAddress, groupIds)
}

export async function denyGroups(
clientAddress: string,
groupIds: string[]
): Promise<void> {
return XMTPModule.denyGroups(clientAddress, groupIds)
}

export async function isGroupAllowed(
clientAddress: string,
groupId: string
): Promise<boolean> {
return XMTPModule.isGroupAllowed(clientAddress, groupId)
}

export async function isGroupDenied(
clientAddress: string,
groupId: string
): Promise<boolean> {
return XMTPModule.isGroupDenied(clientAddress, groupId)
}

export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP)

export * from './XMTP.types'
Expand Down
18 changes: 17 additions & 1 deletion src/lib/Contacts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Client } from './Client'
import { ConsentListEntry } from './ConsentListEntry'
import { ConsentListEntry, ConsentState } from './ConsentListEntry'

Check warning on line 2 in src/lib/Contacts.ts

View workflow job for this annotation

GitHub Actions / lint

'ConsentState' is defined but never used
import * as XMTPModule from '../index'
import { getAddress } from '../utils/address'

Expand Down Expand Up @@ -41,4 +41,20 @@ export default class Contacts {
async consentList(): Promise<ConsentListEntry[]> {
return await XMTPModule.consentList(this.client.address)
}

async allowGroups(groupIds: string[]): Promise<void> {
return await XMTPModule.allowGroups(this.client.address, groupIds)
}

async denyGroups(groupIds: string[]): Promise<void> {
return await XMTPModule.denyGroups(this.client.address, groupIds)
}

async isGroupAllowed(groupId: string): Promise<boolean> {
return await XMTPModule.isGroupAllowed(this.client.address, groupId)
}

async isGroupDenied(groupId: string): Promise<boolean> {
return await XMTPModule.isGroupDenied(this.client.address, groupId)
}
}

0 comments on commit b560c25

Please sign in to comment.