Skip to content

Commit

Permalink
Merge pull request #436 from xmtp/cv/pinned-frame
Browse files Browse the repository at this point in the history
Adding pinned frame metadata field
  • Loading branch information
cameronvoell authored Jul 3, 2024
2 parents c74fc24 + fb74c00 commit a4b8fc0
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 32 deletions.
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.14.2"
implementation "org.xmtp:android:0.14.3"
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"
Expand All @@ -108,9 +108,9 @@ dependencies {
// implementation 'io.grpc:grpc-kotlin-stub:1.4.1'
// implementation 'io.grpc:grpc-okhttp:1.62.2'
// implementation 'io.grpc:grpc-protobuf-lite:1.62.2'
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0'
// implementation 'org.web3j:crypto:5.0.0'
// implementation "net.java.dev.jna:jna:5.14.0@aar"
// api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3'
// api 'org.xmtp:proto-kotlin:3.61.1'
// api 'org.xmtp:proto-kotlin:3.62.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper.Companion.consent
import expo.modules.xmtpreactnativesdk.wrappers.ContentJson
import expo.modules.xmtpreactnativesdk.wrappers.ConversationContainerWrapper
import expo.modules.xmtpreactnativesdk.wrappers.ConversationWrapper
import expo.modules.xmtpreactnativesdk.wrappers.CreateGroupParamsWrapper
import expo.modules.xmtpreactnativesdk.wrappers.DecodedMessageWrapper
import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment
import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment
Expand Down Expand Up @@ -834,25 +835,28 @@ class XMTPModule : Module() {
ConversationWrapper.encode(client, conversation)
}
}
AsyncFunction("createGroup") Coroutine { inboxId: String, peerAddresses: List<String>, permission: String, groupName: String, groupImageUrlSquare: String, groupDescription: String ->
AsyncFunction("createGroup") Coroutine { inboxId: String, peerAddresses: List<String>, permission: String, groupOptionsJson: String ->
withContext(Dispatchers.IO) {
logV("createGroup")
val client = clients[inboxId] ?: throw XMTPException("No client")
val permissionLevel = when (permission) {
"admin_only" -> GroupPermissionPreconfiguration.ADMIN_ONLY
else -> GroupPermissionPreconfiguration.ALL_MEMBERS
}
val createGroupParams = CreateGroupParamsWrapper.createGroupParamsFromJson(groupOptionsJson)
val group = client.conversations.newGroup(
peerAddresses,
permissionLevel,
groupName,
groupImageUrlSquare,
groupDescription
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.groupPinnedFrameUrl
)
GroupWrapper.encode(client, group)
}
}


AsyncFunction("listMemberInboxIds") Coroutine { inboxId: String, groupId: String ->
withContext(Dispatchers.IO) {
logV("listMembers")
Expand Down Expand Up @@ -988,6 +992,26 @@ class XMTPModule : Module() {
}
}

AsyncFunction("groupPinnedFrameUrl") Coroutine { inboxId: String, id: String ->
withContext(Dispatchers.IO) {
logV("groupPinnedFrameUrl")
val client = clients[inboxId] ?: throw XMTPException("No client")
val group = findGroup(inboxId, id)

group?.pinnedFrameUrl
}
}

AsyncFunction("updateGroupPinnedFrameUrl") Coroutine { inboxId: String, id: String, pinnedFrameUrl: String ->
withContext(Dispatchers.IO) {
logV("updateGroupPinnedFrameUrl")
val client = clients[inboxId] ?: throw XMTPException("No client")
val group = findGroup(inboxId, id)

group?.updateGroupPinnedFrameUrl(pinnedFrameUrl)
}
}

AsyncFunction("isGroupActive") Coroutine { inboxId: String, id: String ->
withContext(Dispatchers.IO) {
logV("isGroupActive")
Expand Down Expand Up @@ -1166,6 +1190,16 @@ class XMTPModule : Module() {
}
}

AsyncFunction("updateGroupPinnedFrameUrlPermission") Coroutine { clientInboxId: String, id: String, newPermission: String ->
withContext(Dispatchers.IO) {
logV("updateGroupPinnedFrameUrlPermission")
val client = clients[clientInboxId] ?: throw XMTPException("No client")
val group = findGroup(clientInboxId, id)

group?.updateGroupPinnedFrameUrlPermission(getPermissionOption(newPermission))
}
}

AsyncFunction("permissionPolicySet") Coroutine { inboxId: String, id: String ->
withContext(Dispatchers.IO) {
logV("groupImageUrlSquare")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.JsonParser

class CreateGroupParamsWrapper(
val groupName: String,
val groupImageUrlSquare: String,
val groupDescription: String,
val groupPinnedFrameUrl: String,
) {
companion object {
fun createGroupParamsFromJson(authParams: String): CreateGroupParamsWrapper {
val jsonOptions = JsonParser.parseString(authParams).asJsonObject
return CreateGroupParamsWrapper(
if (jsonOptions.has("name")) jsonOptions.get("name").asString else "",
if (jsonOptions.has("imageUrlSquare")) jsonOptions.get("imageUrlSquare").asString else "",
if (jsonOptions.has("description")) jsonOptions.get("description").asString else "",
if (jsonOptions.has("pinnedFrameUrl")) jsonOptions.get("pinnedFrameUrl").asString else "",
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class GroupWrapper {

companion object {
fun encodeToObj(client: Client, group: Group): Map<String, Any> {
val permissionPolicySet = PermissionPolicySetWrapper.encodeToJsonString(group.permissionPolicySet())
return mapOf(
"clientAddress" to client.address,
"id" to group.id.toHex(),
Expand All @@ -18,10 +17,7 @@ class GroupWrapper {
"version" to "GROUP",
"topic" to group.topic,
"creatorInboxId" to group.creatorInboxId(),
"name" to group.name,
"isActive" to group.isActive(),
"imageUrlSquare" to group.imageUrlSquare,
"description" to group.description
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PermissionPolicySetWrapper {
"updateGroupNamePolicy" to fromPermissionOption(policySet.updateGroupNamePolicy),
"updateGroupDescriptionPolicy" to fromPermissionOption(policySet.updateGroupDescriptionPolicy),
"updateGroupImagePolicy" to fromPermissionOption(policySet.updateGroupImagePolicy),
"updateGroupPinnedFrameUrlPolicy" to fromPermissionOption(policySet.updateGroupPinnedFrameUrlPolicy),
)
}

Expand Down
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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.4-beta1)
- LibXMTP (0.5.4-beta2)
- Logging (1.0.0)
- MessagePacker (0.4.7)
- MMKV (1.3.5):
Expand Down Expand Up @@ -449,16 +449,16 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.13.2):
- XMTP (0.13.3):
- Connect-Swift (= 0.12.0)
- GzipSwift
- LibXMTP (= 0.5.4-beta1)
- LibXMTP (= 0.5.4-beta2)
- web3.swift
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- secp256k1.swift
- XMTP (= 0.13.2)
- XMTP (= 0.13.3)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -711,7 +711,7 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: f080197cea545a7daf3bc8476d97efc3b225a8d8
LibXMTP: 16096f324c99d44712ed40876fe25150f694feab
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb
Expand Down Expand Up @@ -763,8 +763,8 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 76ac90da19ba43afbde1382d57fbcdbdfe9491d8
XMTPReactNative: d3104af9a1504fb42b16c60888993d1d0516afae
XMTP: ca70dd11d709df02999325663e677fe775623d1e
XMTPReactNative: 1b79a6c8748387062ebcebe2c345f77f4998cae2
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2
Expand Down
51 changes: 50 additions & 1 deletion example/src/tests/groupPermissionsTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ test('can not remove a super admin from a group', async () => {

// Now bo can remove Alix from the group
await boGroup.removeMembers([alix.address])
console.log('alix inbox id:' + String(alix.inboxId))
await boGroup.sync()
numMembers = (await boGroup.memberInboxIds()).length
assert(
Expand Down Expand Up @@ -463,3 +462,53 @@ test('can update group permissions', async () => {

return true
})

test('can update group pinned frame', async () => {
// Create clients
const [alix, bo, caro] = await createClients(3)

// Bo creates a group with Alix and Caro
const boGroup = await bo.conversations.newGroup(
[alix.address, caro.address],
{ permissionLevel: 'admin_only' }
)

// Verify that alix can not update the group pinned frame
await alix.conversations.syncGroups()
const alixGroup = (await alix.conversations.listGroups())[0]
try {
await alixGroup.updateGroupPinnedFrameUrl('new pinned frame')
assert(false, 'Alix should not be able to update the group pinned frame')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// expected
}

// Verify that bo can update the group pinned frame
await boGroup.updateGroupPinnedFrameUrl('new pinned frame 2')
await boGroup.sync()
assert(
(await boGroup.groupPinnedFrameUrl()) === 'new pinned frame 2',
`boGroup.groupPinnedFrameUrl should be "new pinned frame 2" but was ${boGroup.groupPinnedFrameUrl}`
)

// Verify that bo can update the pinned frame permission
await boGroup.updateGroupPinnedFrameUrlPermission('allow')
await boGroup.sync()
assert(
(await boGroup.permissionPolicySet()).updateGroupPinnedFrameUrlPolicy ===
'allow',
`boGroup.permissionPolicySet.updateGroupPinnedFrameUrlPolicy should be allow but was ${(await boGroup.permissionPolicySet()).updateGroupPinnedFrameUrlPolicy}`
)

// Verify that Alix can now update pinned frames
await alixGroup.updateGroupPinnedFrameUrl('new pinned frame 3')
await alixGroup.sync()
await boGroup.sync()
assert(
(await boGroup.groupPinnedFrameUrl()) === 'new pinned frame 3',
`alixGroup.groupPinnedFrameUrl should be "new pinned frame 3" but was ${boGroup.groupPinnedFrameUrl}`
)

return true
})
25 changes: 25 additions & 0 deletions ios/Wrappers/CreateGroupParamsWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

struct CreateGroupParamsWrapper {
let groupName: String
let groupImageUrlSquare: String
let groupDescription: String
let groupPinnedFrameUrl: String

static func createGroupParamsFromJson(_ authParams: String) -> CreateGroupParamsWrapper {
let data = authParams.data(using: .utf8) ?? Data()
let jsonOptions = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] ?? [:]

let groupName = jsonOptions["name"] as? String ?? ""
let groupImageUrlSquare = jsonOptions["imageUrlSquare"] as? String ?? ""
let groupDescription = jsonOptions["description"] as? String ?? ""
let groupPinnedFrameUrl = jsonOptions["pinnedFrameUrl"] as? String ?? ""

return CreateGroupParamsWrapper(
groupName: groupName,
groupImageUrlSquare: groupImageUrlSquare,
groupDescription: groupDescription,
groupPinnedFrameUrl: groupPinnedFrameUrl
)
}
}
4 changes: 0 additions & 4 deletions ios/Wrappers/GroupWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import XMTP
// Wrapper around XMTP.Group to allow passing these objects back into react native.
struct GroupWrapper {
static func encodeToObj(_ group: XMTP.Group, client: XMTP.Client) throws -> [String: Any] {
let permissionPolicySet = try PermissionPolicySetWrapper.encodeToJsonString(group.permissionPolicySet())
return [
"clientAddress": client.address,
"id": group.id.toHex,
Expand All @@ -20,10 +19,7 @@ struct GroupWrapper {
"version": "GROUP",
"topic": group.topic,
"creatorInboxId": try group.creatorInboxId(),
"name": try group.groupName(),
"isActive": try group.isActive(),
"imageUrlSquare": try group.groupImageUrlSquare(),
"description": try group.groupDescription(),
]
}

Expand Down
3 changes: 2 additions & 1 deletion ios/Wrappers/PermissionPolicySetWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class PermissionPolicySetWrapper {
"removeAdminPolicy": fromPermissionOption(policySet.removeAdminPolicy),
"updateGroupNamePolicy": fromPermissionOption(policySet.updateGroupNamePolicy),
"updateGroupDescriptionPolicy": fromPermissionOption(policySet.updateGroupDescriptionPolicy),
"updateGroupImagePolicy": fromPermissionOption(policySet.updateGroupImagePolicy)
"updateGroupImagePolicy": fromPermissionOption(policySet.updateGroupImagePolicy),
"updateGroupPinnedFrameUrlPolicy": fromPermissionOption(policySet.updateGroupPinnedFrameUrlPolicy)
]
}

Expand Down
46 changes: 44 additions & 2 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ public class XMTPModule: Module {
}
}

AsyncFunction("createGroup") { (inboxId: String, peerAddresses: [String], permission: String, groupName: String, groupImageUrlSquare: String, groupDescription: String) -> String in
AsyncFunction("createGroup") { (inboxId: String, peerAddresses: [String], permission: String, groupOptionsJson: String) -> String in
guard let client = await clientsManager.getClient(key: inboxId) else {
throw Error.noClient
}
Expand All @@ -709,7 +709,15 @@ public class XMTPModule: Module {
}
}()
do {
let group = try await client.conversations.newGroup(with: peerAddresses, permissions: permissionLevel, name: groupName, imageUrlSquare: groupImageUrlSquare, description: groupDescription)
let createGroupParams = CreateGroupParamsWrapper.createGroupParamsFromJson(groupOptionsJson)
let group = try await client.conversations.newGroup(
with: peerAddresses,
permissions: permissionLevel,
name: createGroupParams.groupName,
imageUrlSquare: createGroupParams.groupImageUrlSquare,
description: createGroupParams.groupDescription,
pinnedFrameUrl: createGroupParams.groupPinnedFrameUrl
)
return try GroupWrapper.encode(group, client: client)
} catch {
print("ERRRO!: \(error.localizedDescription)")
Expand Down Expand Up @@ -877,6 +885,30 @@ public class XMTPModule: Module {

try await group.updateGroupDescription(groupDescription: description)
}

AsyncFunction("groupPinnedFrameUrl") { (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.groupPinnedFrameUrl()
}

AsyncFunction("updateGroupPinnedFrameUrl") { (inboxId: String, id: String, pinnedFrameUrl: 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.updateGroupPinnedFrameUrl(groupPinnedFrameUrl: pinnedFrameUrl)
}

AsyncFunction("isGroupActive") { (inboxId: String, id: String) -> Bool in
guard let client = await clientsManager.getClient(key: inboxId) else {
Expand Down Expand Up @@ -1057,6 +1089,16 @@ public class XMTPModule: Module {
}
try await group.updateGroupDescriptionPermission(newPermissionOption: getPermissionOption(permission: newPermission))
}

AsyncFunction("updateGroupPinnedFrameUrlPermission") { (clientInboxId: String, id: String, newPermission: String) in
guard let client = await clientsManager.getClient(key: clientInboxId) else {
throw Error.noClient
}
guard let group = try await findGroup(inboxId: clientInboxId, id: id) else {
throw Error.conversationNotFound("no group found for \(id)")
}
try await group.updateGroupPinnedFrameUrlPermission(newPermissionOption: getPermissionOption(permission: newPermission))
}

AsyncFunction("permissionPolicySet") { (inboxId: String, id: String) async throws -> String in

Expand Down
Loading

0 comments on commit a4b8fc0

Please sign in to comment.