Skip to content

Commit

Permalink
Merge pull request #402 from xmtp/np/group-members
Browse files Browse the repository at this point in the history
  • Loading branch information
nplasterer authored Jun 3, 2024
2 parents 59505df + 050a773 commit 4ba132d
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import com.facebook.common.util.Hex
import expo.modules.xmtpreactnativesdk.wrappers.MemberWrapper
import org.xmtp.android.library.messages.MessageDeliveryStatus
import org.xmtp.android.library.messages.Topic
import org.xmtp.android.library.push.Service
Expand Down Expand Up @@ -805,6 +806,15 @@ class XMTPModule : Module() {
}
}

AsyncFunction("listGroupMembers") Coroutine { clientAddress: String, groupId: String ->
withContext(Dispatchers.IO) {
logV("listGroupMembers")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val group = findGroup(clientAddress, groupId)
group?.members()?.map { MemberWrapper.encode(it) }
}
}

AsyncFunction("syncGroups") Coroutine { clientAddress: String ->
withContext(Dispatchers.IO) {
logV("syncGroups")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder
import org.xmtp.android.library.Client
import org.xmtp.android.library.Conversation
import org.xmtp.android.library.Group
import org.xmtp.android.library.codecs.Attachment
import org.xmtp.android.library.toHex
import uniffi.xmtpv3.GroupPermissions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.GsonBuilder
import org.xmtp.android.library.libxmtp.Member
import org.xmtp.android.library.libxmtp.PermissionLevel

class MemberWrapper {
companion object {
fun encodeToObj(member: Member): Map<String, Any> {
val permissionString = when (member.permissionLevel) {
PermissionLevel.MEMBER -> "member"
PermissionLevel.ADMIN -> "admin"
PermissionLevel.SUPER_ADMIN -> "super_admin"
}
return mapOf(
"inboxId" to member.inboxId,
"addresses" to member.addresses,
"permissionLevel" to permissionString
)
}

fun encode(member: Member): String {
val gson = GsonBuilder().create()
val obj = encodeToObj(member)
return gson.toJson(obj)
}
}
}
20 changes: 20 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,26 @@ test('who added me to a group', async () => {
return true
})

test('can get members of a group', async () => {
const [alixClient, boClient] = await createClients(2)
const group = await alixClient.conversations.newGroup([boClient.address])

const members = await group.members()

assert(members.length === 2, `Should be 2 members but was ${members.length}`)
assert(
members[0].addresses[0].toLocaleLowerCase ===
boClient.address.toLocaleLowerCase,
`Should be ${boClient.address} but was ${members[0].addresses[0]}`
)
assert(
members[0].permissionLevel === "admin",
`Should be admin but was ${members[0].permissionLevel}`
)

return true
})

test('can message in a group', async () => {
// Create three MLS enabled Clients
const [alixClient, boClient, caroClient] = await createClients(3)
Expand Down
37 changes: 37 additions & 0 deletions ios/Wrappers/MemberWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// MemberWrapper.swift
// XMTPReactNative
//
// Created by Naomi Plasterer on 6/3/24.
//

import Foundation
import XMTP

// Wrapper around XMTP.Group to allow passing these objects back into react native.
struct MemberWrapper {
static func encodeToObj(_ member: XMTP.Member) throws -> [String: Any] {
let permissionString = switch member.permissionLevel {
case .Member:
"member"
case .Admin:
"admin"
case .SuperAdmin:
"super_admin"
}
return [
"inboxId": member.inboxId,
"addresses": member.addresses,
"permissionLevel": permissionString,
]
}

static func encode(_ member: XMTP.Member) throws -> String {
let obj = try encodeToObj(member)
let data = try JSONSerialization.data(withJSONObject: obj)
guard let result = String(data: data, encoding: .utf8) else {
throw WrapperError.encodeError("could not encode member")
}
return result
}
}
14 changes: 14 additions & 0 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,20 @@ public class XMTPModule: Module {
return try group.members.map(\.inboxId)
}

AsyncFunction("listGroupMembers") { (clientAddress: String, groupId: String) -> [String] in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}

guard let group = try await findGroup(clientAddress: clientAddress, id: groupId) else {
throw Error.conversationNotFound("no group found for \(groupId)")
}
return try group.members.compactMap { member in
return try MemberWrapper.encode(member)
}
}


AsyncFunction("syncGroups") { (clientAddress: String) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
Expand Down
13 changes: 13 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './lib/ConversationContainer'
import { DecodedMessage, MessageDeliveryStatus } from './lib/DecodedMessage'
import { Group } from './lib/Group'
import { Member } from './lib/Member'
import type { Query } from './lib/Query'
import { ConversationSendPayload } from './lib/types'
import { DefaultContentTypes } from './lib/types/DefaultContentType'
Expand Down Expand Up @@ -147,6 +148,17 @@ export async function listMemberInboxIds<
return XMTPModule.listMemberInboxIds(client.address, id)
}

export async function listGroupMembers(
clientAddress: string,
id: string
): Promise<Member[]> {
const members = await XMTPModule.listGroupMembers(clientAddress, id)

return members.map((json: string) => {
return Member.from(json)
})
}

export async function sendMessageToGroup(
clientAddress: string,
groupId: string,
Expand Down Expand Up @@ -893,3 +905,4 @@ export { Query } from './lib/Query'
export { XMTPPush } from './lib/XMTPPush'
export { ConsentListEntry, DecodedMessage, MessageDeliveryStatus }
export { Group } from './lib/Group'
export { Member } from './lib/Member'
5 changes: 5 additions & 0 deletions src/lib/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ConversationContainer,
} from './ConversationContainer'
import { DecodedMessage, MessageDeliveryStatus } from './DecodedMessage'
import { Member } from './Member'
import { ConversationSendPayload } from './types/ConversationCodecs'
import { DefaultContentTypes } from './types/DefaultContentType'
import { EventTypes } from './types/EventTypes'
Expand Down Expand Up @@ -295,4 +296,8 @@ export class Group<
async isDenied(): Promise<boolean> {
return await XMTP.isGroupDenied(this.client.address, this.id)
}

async members(): Promise<Member[]> {
return await XMTP.listGroupMembers(this.client.address, this.id)
}
}
20 changes: 20 additions & 0 deletions src/lib/Member.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export class Member {
inboxId: string
addresses: string[]
permissionLevel: 'member' | 'admin' | 'super_admin'

constructor(
inboxId: string,
addresses: string[],
permissionLevel: 'member' | 'admin' | 'super_admin'
) {
this.inboxId = inboxId
this.addresses = addresses
this.permissionLevel = permissionLevel
}

static from(json: string): Member {
const entry = JSON.parse(json)
return new Member(entry.inboxId, entry.addresses, entry.permissionLevel)
}
}

0 comments on commit 4ba132d

Please sign in to comment.