Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Group Members #402

Merged
merged 4 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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",

Check warning on line 255 in example/src/tests/groupTests.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `"admin"` with `'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 @@
} 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 @@
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 @@ -850,7 +862,7 @@
return XMTPModule.isInboxDenied(clientAddress, inboxId)
}


Check warning on line 865 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `⏎`
export async function processGroupMessage<
ContentTypes extends DefaultContentTypes = DefaultContentTypes,
>(
Expand Down Expand Up @@ -893,3 +905,4 @@
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)
}
}
Loading