From 7162ce4c5ef709659ac370969f07e7b846826d36 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 13 Jun 2024 12:02:57 -0700 Subject: [PATCH 1/4] add the android bridge code --- android/build.gradle | 2 +- .../modules/xmtpreactnativesdk/XMTPModule.kt | 29 +++++++++++++++++-- .../wrappers/GroupWrapper.kt | 3 +- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 0581e0935..e66f798da 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:0.12.4" + implementation "org.xmtp:android:0.12.5" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.facebook.react:react-native:0.71.3' implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1" diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 82495fd94..c1642afe6 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -785,7 +785,7 @@ class XMTPModule : Module() { ConversationWrapper.encode(client, conversation) } } - AsyncFunction("createGroup") Coroutine { inboxId: String, peerAddresses: List, permission: String -> + AsyncFunction("createGroup") Coroutine { inboxId: String, peerAddresses: List, permission: String, groupName: String, groupImageUrlSquare: String -> withContext(Dispatchers.IO) { logV("createGroup") val client = clients[inboxId] ?: throw XMTPException("No client") @@ -793,7 +793,12 @@ class XMTPModule : Module() { "admin_only" -> GroupPermissions.ADMIN_ONLY else -> GroupPermissions.ALL_MEMBERS } - val group = client.conversations.newGroup(peerAddresses, permissionLevel) + val group = client.conversations.newGroup( + peerAddresses, + permissionLevel, + groupName, + groupImageUrlSquare + ) GroupWrapper.encode(client, group) } } @@ -893,6 +898,26 @@ class XMTPModule : Module() { } } + AsyncFunction("groupImageUrlSquare") Coroutine { inboxId: String, id: String -> + withContext(Dispatchers.IO) { + logV("groupImageUrlSquare") + val client = clients[inboxId] ?: throw XMTPException("No client") + val group = findGroup(inboxId, id) + + group?.imageUrlSquare + } + } + + AsyncFunction("updateGroupImageUrlSquare") Coroutine { inboxId: String, id: String, groupImageUrl: String -> + withContext(Dispatchers.IO) { + logV("updateGroupImageUrlSquare") + val client = clients[inboxId] ?: throw XMTPException("No client") + val group = findGroup(inboxId, id) + + group?.updateGroupImageUrlSquare(groupImageUrl) + } + } + AsyncFunction("isGroupActive") Coroutine { inboxId: String, id: String -> withContext(Dispatchers.IO) { logV("isGroupActive") diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt index 6214afbd6..94d73aa72 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt @@ -26,7 +26,8 @@ class GroupWrapper { "permissionLevel" to permissionString, "creatorInboxId" to group.creatorInboxId(), "name" to group.name, - "isActive" to group.isActive() + "isActive" to group.isActive(), + "imageUrlSquare" to group.imageUrlSquare ) } From 3ec9aa5b6d0756cd31fee3dcfeb6412a453047b8 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 13 Jun 2024 12:15:03 -0700 Subject: [PATCH 2/4] add the sdk side of the features --- src/index.ts | 23 +++++++++++++++++++++-- src/lib/Conversations.ts | 7 +++++-- src/lib/Group.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index f5ef980b3..058363456 100644 --- a/src/index.ts +++ b/src/index.ts @@ -127,7 +127,9 @@ export async function createGroup< >( client: Client, peerAddresses: string[], - permissionLevel: 'all_members' | 'admin_only' = 'all_members' + permissionLevel: 'all_members' | 'admin_only' = 'all_members', + name: string = '', + imageUrlSquare: string = '' ): Promise> { return new Group( client, @@ -135,7 +137,9 @@ export async function createGroup< await XMTPModule.createGroup( client.inboxId, peerAddresses, - permissionLevel + permissionLevel, + name, + imageUrlSquare ) ) ) @@ -254,6 +258,21 @@ export async function removeGroupMembersByInboxId( return XMTPModule.removeGroupMembersByInboxId(inboxId, id, inboxIds) } +export function groupImageUrlSquare( + inboxId: string, + id: string +): string | PromiseLike { + return XMTPModule.groupImageUrlSquare(inboxId, id) +} + +export function updateGroupImageUrlSquare( + inboxId: string, + id: string, + imageUrlSquare: string +): Promise { + return XMTPModule.updateGroupImageUrlSquare(inboxId, id, imageUrlSquare) +} + export function groupName( inboxId: string, id: string diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index 63cc14c15..e75c97bd8 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -13,6 +13,7 @@ import { ConversationContext } from '../XMTP.types' import * as XMTPModule from '../index' import { ContentCodec } from '../index' import { getAddress } from '../utils/address' +import { CreateGroupOptions } from './types/CreateGroupOptions' export default class Conversations< ContentTypes extends ContentCodec[] = [], @@ -151,12 +152,14 @@ export default class Conversations< */ async newGroup( peerAddresses: string[], - permissionLevel: 'all_members' | 'admin_only' = 'all_members' + opts: CreateGroupOptions ): Promise> { return await XMTPModule.createGroup( this.client, peerAddresses, - permissionLevel + opts.permissionLevel, + opts.name, + opts.imageUrlSquare ) } diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 372c13ce3..62824beab 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -26,6 +26,7 @@ export class Group< permissionLevel: 'all_members' | 'admin_only' name: string isGroupActive: boolean + imageUrlSquare: string constructor( client: XMTP.Client, @@ -38,6 +39,7 @@ export class Group< topic: string name: string isGroupActive: boolean + imageUrlSquare: string } ) { this.client = client @@ -49,6 +51,7 @@ export class Group< this.permissionLevel = params.permissionLevel this.name = params.name this.isGroupActive = params.isGroupActive + this.imageUrlSquare = params.imageUrlSquare } /** @@ -235,6 +238,30 @@ export class Group< return XMTP.updateGroupName(this.client.inboxId, this.id, groupName) } + /** + * Returns the group image url square. + * To get the latest group image url square from the network, call sync() first. + * @returns {string} A Promise that resolves to the group name. + */ + async groupImageUrlSquare(): Promise { + return XMTP.groupImageUrlSquare(this.client.inboxId, this.id) + } + + /** + * Updates the group image url square. + * Will throw if the user does not have the required permissions. + * @param {string} imageUrlSquare new group profile image url + * @returns + */ + + async updateGroupImageUrlSquare(imageUrlSquare: string): Promise { + return XMTP.updateGroupImageUrlSquare( + this.client.inboxId, + this.id, + imageUrlSquare + ) + } + /** * Returns whether the group is active. * To get the latest active status from the network, call sync() first From 69c241116fd71947c18fe0907cb4e1c5f635c9d3 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 13 Jun 2024 12:26:18 -0700 Subject: [PATCH 3/4] write involved test for it --- example/src/tests/groupTests.ts | 57 ++++++++++++++++++++++++++++- src/lib/Conversations.ts | 8 ++-- src/lib/types/CreateGroupOptions.ts | 5 +++ 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/lib/types/CreateGroupOptions.ts diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index b351309bb..92eafb2a1 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -1004,12 +1004,67 @@ test('can stream all messages', async () => { return true }) +test('can make a group with metadata', async () => { + const [alix, bo] = await createClients(2) + + const alixGroup = await alix.conversations.newGroup([bo.address], { + name: 'Start Name', + imageUrlSquare: 'starturl.com', + }) + + const groupName1 = await alixGroup.groupName() + const groupImageUrl1 = await alixGroup.groupImageUrlSquare() + assert( + groupName1 === 'Start Name', + `the group should start with a name of Start Name not ${groupName1}` + ) + + assert( + groupImageUrl1 === 'starturl.com', + `the group should start with a name of starturl.com not ${groupImageUrl1}` + ) + + await alixGroup.updateGroupName('New Name') + await alixGroup.updateGroupImageUrlSquare('newurl.com') + await alixGroup.sync() + await bo.conversations.syncGroups() + const boGroups = await bo.conversations.listGroups() + const boGroup = boGroups[0] + await boGroup.sync() + + const groupName2 = await alixGroup.groupName() + const groupImageUrl2 = await alixGroup.groupImageUrlSquare() + assert( + groupName2 === 'New Name', + `the group should start with a name of New Name not ${groupName2}` + ) + + assert( + groupImageUrl2 === 'newurl.com', + `the group should start with a name of newurl.com not ${groupImageUrl2}` + ) + + const groupName3 = await boGroup.groupName() + const groupImageUrl3 = await boGroup.groupImageUrlSquare() + assert( + groupName3 === 'New Name', + `the group should start with a name of New Name not ${groupName3}` + ) + + assert( + groupImageUrl3 === 'newurl.com', + `the group should start with a name of newurl.com not ${groupImageUrl3}` + ) + + return true +}) + test('can make a group with admin permissions', async () => { const [adminClient, anotherClient] = await createClients(2) const group = await adminClient.conversations.newGroup( [anotherClient.address], - 'admin_only' + { permissionLevel: 'admin_only' } ) if (group.permissionLevel !== 'admin_only') { diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index e75c97bd8..925b7e1cf 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -152,14 +152,14 @@ export default class Conversations< */ async newGroup( peerAddresses: string[], - opts: CreateGroupOptions + opts?: CreateGroupOptions | undefined ): Promise> { return await XMTPModule.createGroup( this.client, peerAddresses, - opts.permissionLevel, - opts.name, - opts.imageUrlSquare + opts?.permissionLevel, + opts?.name, + opts?.imageUrlSquare ) } diff --git a/src/lib/types/CreateGroupOptions.ts b/src/lib/types/CreateGroupOptions.ts new file mode 100644 index 000000000..106cfd732 --- /dev/null +++ b/src/lib/types/CreateGroupOptions.ts @@ -0,0 +1,5 @@ +export type CreateGroupOptions = { + permissionLevel?: 'all_members' | 'admin_only' | undefined + name?: string | undefined + imageUrlSquare?: string | undefined +} From 4a0017d491601e8774a202f72a3f1996bbdd4a86 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 13 Jun 2024 12:52:26 -0700 Subject: [PATCH 4/4] fix: group image url --- example/ios/Podfile.lock | 14 +++++++------- ios/Wrappers/GroupWrapper.swift | 3 ++- ios/XMTPModule.swift | 29 ++++++++++++++++++++++++++--- ios/XMTPReactNative.podspec | 2 +- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a2f8a1719..6304edf6c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -56,7 +56,7 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (0.5.1-beta1) + - LibXMTP (0.5.1-beta2) - Logging (1.0.0) - MessagePacker (0.4.7) - MMKV (1.3.5): @@ -449,16 +449,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.11.6): + - XMTP (0.11.7): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.5.1-beta1) + - LibXMTP (= 0.5.1-beta2) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.11.6) + - XMTP (= 0.11.7) - Yoga (1.14.0) DEPENDENCIES: @@ -711,7 +711,7 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: f897dba3ea05dd5f8b9d098176cd9cb7c73ad505 + LibXMTP: 954acfb393be3b19bf44ea9009af558c00222450 Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 5986c7bc1400b8b054569d4c7c6d2f673d992be1 - XMTPReactNative: ed4824399c26767008b8266992085461f0cece98 + XMTP: b8ab59997ee95106778f445992fa00adce6c2d71 + XMTPReactNative: fc0eae046a9a3e3031c6d3b9921e15a6d5b13e09 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 diff --git a/ios/Wrappers/GroupWrapper.swift b/ios/Wrappers/GroupWrapper.swift index f0dd86b0e..dd98115df 100644 --- a/ios/Wrappers/GroupWrapper.swift +++ b/ios/Wrappers/GroupWrapper.swift @@ -27,7 +27,8 @@ struct GroupWrapper { "permissionLevel": permissionString, "creatorInboxId": try group.creatorInboxId(), "name": try group.groupName(), - "isActive": try group.isActive() + "isActive": try group.isActive(), + "imageUrlSquare": try group.groupImageUrlSquare(), ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 509277ada..9032eefa4 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -659,7 +659,7 @@ public class XMTPModule: Module { } } - AsyncFunction("createGroup") { (inboxId: String, peerAddresses: [String], permission: String) -> String in + AsyncFunction("createGroup") { (inboxId: String, peerAddresses: [String], permission: String, groupName: String, groupImageUrlSquare: String) -> String in guard let client = await clientsManager.getClient(key: inboxId) else { throw Error.noClient } @@ -672,7 +672,7 @@ public class XMTPModule: Module { } }() do { - let group = try await client.conversations.newGroup(with: peerAddresses, permissions: permissionLevel) + let group = try await client.conversations.newGroup(with: peerAddresses, permissions: permissionLevel, name: groupName, imageUrlSquare: groupImageUrlSquare) return try GroupWrapper.encode(group, client: client) } catch { print("ERRRO!: \(error.localizedDescription)") @@ -769,7 +769,6 @@ public class XMTPModule: Module { try await group.removeMembersByInboxId(inboxIds: inboxIds) } - AsyncFunction("groupName") { (inboxId: String, id: String) -> String in guard let client = await clientsManager.getClient(key: inboxId) else { throw Error.noClient @@ -794,6 +793,30 @@ public class XMTPModule: Module { try await group.updateGroupName(groupName: groupName) } + AsyncFunction("groupImageUrlSquare") { (inboxId: String, id: String) -> String in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + + guard let group = try await findGroup(inboxId: inboxId, id: id) else { + throw Error.conversationNotFound("no group found for \(id)") + } + + return try group.groupImageUrlSquare() + } + + AsyncFunction("updateGroupImageUrlSquare") { (inboxId: String, id: String, groupImageUrl: String) in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + + guard let group = try await findGroup(inboxId: inboxId, id: id) else { + throw Error.conversationNotFound("no group found for \(id)") + } + + try await group.updateGroupImageUrlSquare(imageUrlSquare: groupImageUrl) + } + AsyncFunction("isGroupActive") { (inboxId: String, id: String) -> Bool in guard let client = await clientsManager.getClient(key: inboxId) else { throw Error.noClient diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index 2f02bee6b..0cf94430b 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency 'secp256k1.swift' s.dependency "MessagePacker" - s.dependency "XMTP", "= 0.11.6" + s.dependency "XMTP", "= 0.11.7" end