Skip to content

Commit

Permalink
Merge pull request #640 from xmtp/rygine/updates
Browse files Browse the repository at this point in the history
Upgrade MLS node bindings
  • Loading branch information
rygine authored Jul 2, 2024
2 parents ccabfba + 4ec046b commit da425c5
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 30 deletions.
9 changes: 9 additions & 0 deletions .changeset/warm-suits-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@xmtp/mls-client": patch
---

- Added conversation descriptions
- Fixed DB locking issues
- Fixed invalid policy error
- Removed Admin status from group creators (Super Admin only)
- Made content type optional when sending messages
2 changes: 1 addition & 1 deletion packages/mls-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"dependencies": {
"@xmtp/content-type-primitives": "^1.0.1",
"@xmtp/content-type-text": "^1.0.0",
"@xmtp/mls-client-bindings-node": "^0.0.7",
"@xmtp/mls-client-bindings-node": "^0.0.8",
"@xmtp/proto": "^3.61.1"
},
"devDependencies": {
Expand Down
24 changes: 22 additions & 2 deletions packages/mls-client/src/Conversation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ContentTypeId } from '@xmtp/content-type-primitives'
import { ContentTypeText } from '@xmtp/content-type-text'
import type {
NapiGroup,
NapiListMessagesOptions,
Expand Down Expand Up @@ -37,6 +38,14 @@ export class Conversation {
return this.#group.updateGroupImageUrlSquare(imageUrl)
}

get description() {
return this.#group.groupDescription()
}

async updateDescription(description: string) {
return this.#group.updateGroupDescription(description)
}

get isActive() {
return this.#group.isActive()
}
Expand Down Expand Up @@ -137,8 +146,19 @@ export class Conversation {
return this.#group.removeSuperAdmin(inboxId)
}

async send(content: any, contentType: ContentTypeId) {
return this.#group.send(this.#client.encodeContent(content, contentType))
async send(content: any, contentType?: ContentTypeId) {
if (typeof content !== 'string' && !contentType) {
throw new Error(
'Content type is required when sending content other than text'
)
}

const encodedContent =
typeof content === 'string'
? this.#client.encodeContent(content, contentType ?? ContentTypeText)
: this.#client.encodeContent(content, contentType!)

return this.#group.send(encodedContent)
}

messages(options?: NapiListMessagesOptions): DecodedMessage[] {
Expand Down
86 changes: 74 additions & 12 deletions packages/mls-client/test/Conversation.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { ContentTypeText } from '@xmtp/content-type-text'
import { describe, expect, it } from 'vitest'
import { createRegisteredClient, createUser } from '@test/helpers'
import {
ContentTypeTest,
createRegisteredClient,
createUser,
TestCodec,
} from '@test/helpers'

describe('Conversation', () => {
it('should update conversation name', async () => {
Expand Down Expand Up @@ -55,6 +59,32 @@ describe('Conversation', () => {
expect(conversation2.messages().length).toBe(1)
})

it('should update conversation description', async () => {
const user1 = createUser()
const user2 = createUser()
const client1 = await createRegisteredClient(user1)
const client2 = await createRegisteredClient(user2)
const conversation = await client1.conversations.newConversation([
user2.account.address,
])
const newDescription = 'foo'
await conversation.updateDescription(newDescription)
expect(conversation.description).toBe(newDescription)
const messages = conversation.messages()
expect(messages.length).toBe(2)

await client2.conversations.sync()
const conversations = await client2.conversations.list()
expect(conversations.length).toBe(1)

const conversation2 = conversations[0]
expect(conversation2).toBeDefined()
await conversation2.sync()
expect(conversation2.id).toBe(conversation.id)
expect(conversation2.description).toBe(newDescription)
expect(conversation2.messages().length).toBe(1)
})

it('should add and remove members', async () => {
const user1 = createUser()
const user2 = createUser()
Expand Down Expand Up @@ -131,7 +161,7 @@ describe('Conversation', () => {
])

const text = 'gm'
await conversation.send(text, ContentTypeText)
await conversation.send(text)

const messages = conversation.messages()
expect(messages.length).toBe(2)
Expand All @@ -151,6 +181,38 @@ describe('Conversation', () => {
expect(messages2[0].content).toBe(text)
})

it('should require content type when sending non-string content', async () => {
const user1 = createUser()
const user2 = createUser()
const client1 = await createRegisteredClient(user1, {
codecs: [new TestCodec()],
})
await createRegisteredClient(user2)
const conversation = await client1.conversations.newConversation([
user2.account.address,
])

await expect(() => conversation.send(1)).rejects.toThrow()
await expect(() => conversation.send({ foo: 'bar' })).rejects.toThrow()
await expect(
conversation.send({ foo: 'bar' }, ContentTypeTest)
).resolves.not.toThrow()
})

it('should throw when sending content without a codec', async () => {
const user1 = createUser()
const user2 = createUser()
const client1 = await createRegisteredClient(user1)
await createRegisteredClient(user2)
const conversation = await client1.conversations.newConversation([
user2.account.address,
])

await expect(
conversation.send({ foo: 'bar' }, ContentTypeTest)
).rejects.toThrow()
})

it('should stream messages', async () => {
const user1 = createUser()
const user2 = createUser()
Expand All @@ -167,8 +229,8 @@ describe('Conversation', () => {

const stream = conversation2[0].stream()

await conversation.send('gm', ContentTypeText)
await conversation.send('gm2', ContentTypeText)
await conversation.send('gm')
await conversation.send('gm2')

let count = 0
for await (const message of stream) {
Expand All @@ -194,21 +256,21 @@ describe('Conversation', () => {
user2.account.address,
])

expect(conversation.isAdmin(client1.inboxId)).toBe(true)
expect(conversation.isSuperAdmin(client1.inboxId)).toBe(true)
expect(conversation.superAdmins.length).toBe(1)
expect(conversation.superAdmins).toContain(client1.inboxId)
expect(conversation.isAdmin(client1.inboxId)).toBe(false)
expect(conversation.isAdmin(client2.inboxId)).toBe(false)
expect(conversation.admins.length).toBe(1)
expect(conversation.admins).toContain(client1.inboxId)
expect(conversation.admins.length).toBe(0)

await conversation.addAdmin(client2.inboxId)
expect(conversation.isAdmin(client2.inboxId)).toBe(true)
expect(conversation.admins.length).toBe(2)
expect(conversation.admins).toContain(client1.inboxId)
expect(conversation.admins.length).toBe(1)
expect(conversation.admins).toContain(client2.inboxId)

await conversation.removeAdmin(client2.inboxId)
expect(conversation.isAdmin(client2.inboxId)).toBe(false)
expect(conversation.admins.length).toBe(1)
expect(conversation.admins).toContain(client1.inboxId)
expect(conversation.admins.length).toBe(0)
})

it('should add and remove super admins', async () => {
Expand Down
18 changes: 14 additions & 4 deletions packages/mls-client/test/Conversations.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ContentTypeText } from '@xmtp/content-type-text'
import { GroupPermissions } from '@xmtp/mls-client-bindings-node'
import { describe, expect, it } from 'vitest'
import { createRegisteredClient, createUser } from '@test/helpers'
Expand Down Expand Up @@ -78,7 +77,7 @@ describe('Conversations', () => {
const group = await client1.conversations.newConversation([
user2.account.address,
])
const messageId = await group.send('gm!', ContentTypeText)
const messageId = await group.send('gm!')
expect(messageId).toBeDefined()

const message = client1.conversations.getMessageById(messageId)
Expand Down Expand Up @@ -138,6 +137,17 @@ describe('Conversations', () => {
expect(groupWithPermissions.permissions.policyType).toBe(
GroupPermissions.GroupCreatorIsAdmin
)

const groupWithDescription = await client1.conversations.newConversation(
[user2.account.address],
{
groupDescription: 'foo',
}
)
expect(groupWithDescription).toBeDefined()
expect(groupWithDescription.name).toBe('')
expect(groupWithDescription.imageUrl).toBe('')
expect(groupWithDescription.description).toBe('foo')
})

it('should stream new conversations', async () => {
Expand Down Expand Up @@ -193,8 +203,8 @@ describe('Conversations', () => {
await client3.conversations.sync()
const groups3 = await client3.conversations.list()

await groups2[0].send('gm!', ContentTypeText)
await groups3[0].send('gm2!', ContentTypeText)
await groups2[0].send('gm!')
await groups3[0].send('gm2!')

let count = 0

Expand Down
59 changes: 53 additions & 6 deletions packages/mls-client/test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
ContentTypeId,
type ContentCodec,
type EncodedContent,
} from '@xmtp/content-type-primitives'
import { createWalletClient, http, toBytes } from 'viem'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
import { Client, type XmtpEnv } from '@/Client'
import { Client, type ClientOptions } from '@/Client'

const __dirname = dirname(fileURLToPath(import.meta.url))

Expand Down Expand Up @@ -33,14 +38,22 @@ export const getSignature = async (client: Client, user: User) => {
return null
}

export const createClient = async (user: User, env?: XmtpEnv) =>
Client.create(user.account.address, {
env: env ?? 'local',
export const createClient = async (user: User, options?: ClientOptions) => {
const opts = {
...options,
env: options?.env ?? 'local',
}
return Client.create(user.account.address, {
...opts,
dbPath: join(__dirname, `./test-${user.account.address}.db3`),
})
}

export const createRegisteredClient = async (user: User, env?: XmtpEnv) => {
const client = await createClient(user, env)
export const createRegisteredClient = async (
user: User,
options?: ClientOptions
) => {
const client = await createClient(user, options)
if (!client.isRegistered) {
const signature = await getSignature(client, user)
if (signature) {
Expand All @@ -50,3 +63,37 @@ export const createRegisteredClient = async (user: User, env?: XmtpEnv) => {
}
return client
}

export const ContentTypeTest = new ContentTypeId({
authorityId: 'xmtp.org',
typeId: 'test',
versionMajor: 1,
versionMinor: 0,
})

export class TestCodec implements ContentCodec<Record<string, string>> {
get contentType(): ContentTypeId {
return ContentTypeTest
}

encode(content: Record<string, string>): EncodedContent {
return {
type: this.contentType,
parameters: {},
content: new TextEncoder().encode(JSON.stringify(content)),
}
}

decode(content: EncodedContent) {
const decoded = new TextDecoder().decode(content.content)
return JSON.parse(decoded)
}

fallback() {
return undefined
}

shouldPush() {
return false
}
}
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2881,10 +2881,10 @@ __metadata:
languageName: node
linkType: hard

"@xmtp/mls-client-bindings-node@npm:^0.0.7":
version: 0.0.7
resolution: "@xmtp/mls-client-bindings-node@npm:0.0.7"
checksum: 10/5730868ebac704b967cb0763affc38e2116db69568307d97d77eaf1301718a39c308be9b9aa5f3a2550123e0df8e9ff351a06d9f4bedcd4d4723e5764dfec786
"@xmtp/mls-client-bindings-node@npm:^0.0.8":
version: 0.0.8
resolution: "@xmtp/mls-client-bindings-node@npm:0.0.8"
checksum: 10/58bbe484844e08ae1bd9d22388fb9081662ef96debd29fcb375c88de66d27da1dc94da465add6c54ab926f8b100b0c6e717f6b500706705c7b08ea1021e70e34
languageName: node
linkType: hard

Expand All @@ -2901,7 +2901,7 @@ __metadata:
"@vitest/coverage-v8": "npm:^1.6.0"
"@xmtp/content-type-primitives": "npm:^1.0.1"
"@xmtp/content-type-text": "npm:^1.0.0"
"@xmtp/mls-client-bindings-node": "npm:^0.0.7"
"@xmtp/mls-client-bindings-node": "npm:^0.0.8"
"@xmtp/proto": "npm:^3.61.1"
"@xmtp/xmtp-js": "workspace:^"
eslint: "npm:^8.57.0"
Expand Down

0 comments on commit da425c5

Please sign in to comment.