From fb606b5151078d8ae42fd4dc44a9e1bc2c5e5220 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 08:37:45 -0800 Subject: [PATCH] get the RN side all compiling --- .../wrappers/DecodedMessageWrapper.kt | 2 +- src/index.ts | 2 +- src/lib/Client.ts | 526 ++---------------- src/lib/Contacts.ts | 76 --- src/lib/Conversation.ts | 4 +- src/lib/Conversations.ts | 414 +++----------- src/lib/DecodedMessage.ts | 12 +- src/lib/Dm.ts | 56 +- src/lib/Group.ts | 74 +-- src/lib/InboxState.ts | 2 +- src/lib/Member.ts | 2 +- src/lib/PrivatePreferences.ts | 42 ++ src/lib/types/MessagesOptions.ts | 4 +- src/utils/address.ts | 2 +- 14 files changed, 213 insertions(+), 1005 deletions(-) delete mode 100644 src/lib/Contacts.ts create mode 100644 src/lib/PrivatePreferences.ts diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt index d2a7e6f54..d0f1662f7 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt @@ -23,7 +23,7 @@ class DecodedMessageWrapper { "contentTypeId" to model.encodedContent.type.description, "content" to ContentJson(model.encodedContent).toJsonMap(), "senderAddress" to model.senderAddress, - "sent" to model.sent.time, + "sentNs" to model.sent.time, "fallback" to fallback, "deliveryStatus" to model.deliveryStatus.toString() ) diff --git a/src/index.ts b/src/index.ts index 1d0adda46..48df34727 100644 --- a/src/index.ts +++ b/src/index.ts @@ -581,7 +581,7 @@ export async function canMessage( } export async function getOrCreateInboxId( - address: InboxId, + address: Address, environment: XMTPEnvironment ): Promise { return await XMTPModule.getOrCreateInboxId(getAddress(address), environment) diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 429ffb7fe..accbe1844 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -2,21 +2,19 @@ import { splitSignature } from '@ethersproject/bytes' import { Subscription } from 'expo-modules-core' import type { WalletClient } from 'viem' -import Contacts from './Contacts' import type { DecryptedLocalAttachment, EncryptedLocalAttachment, - PreparedLocalMessage, } from './ContentCodec' import Conversations from './Conversations' import { InboxState } from './InboxState' import { TextCodec } from './NativeCodecs/TextCodec' -import { Query } from './Query' +import PrivatePreferences from './PrivatePreferences' import { Signer, getSigner } from './Signer' import { DefaultContentTypes } from './types/DefaultContentType' import { hexToBytes } from './util' import * as XMTPModule from '../index' -import { DecodedMessage } from '../index' +import { Address } from '../index' declare const Buffer @@ -36,130 +34,19 @@ export class Client< installationId: string dbPath: string conversations: Conversations - contacts: Contacts + preferences: PrivatePreferences codecRegistry: { [key: string]: XMTPModule.ContentCodec } private static signSubscription: Subscription | null = null private static authSubscription: Subscription | null = null - /** - * Creates a new instance of the Client class using the provided signer. - * - * @param {Signer} signer - The signer object used for authentication and message signing. - * @param {Partial} opts - Optional configuration options for the Client. - * @returns {Promise} A Promise that resolves to a new Client instance. - * - * See {@link https://xmtp.org/docs/build/authentication#create-a-client | XMTP Docs} for more information. - */ - static async create< - ContentCodecs extends DefaultContentTypes = DefaultContentTypes, - >( - wallet: Signer | WalletClient | null, - options: ClientOptions & { codecs?: ContentCodecs } - ): Promise> { - if ( - options.enableV3 === true && - (options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32) - ) { - throw new Error('Must pass an encryption key that is exactly 32 bytes.') - } - const { enableSubscription, createSubscription, authInboxSubscription } = - this.setupSubscriptions(options) - const signer = getSigner(wallet) - if (!signer) { - throw new Error('Signer is not configured') - } - return new Promise>((resolve, reject) => { - ;(async () => { - this.signSubscription = XMTPModule.emitter.addListener( - 'sign', - async (message: { id: string; message: string }) => { - const request: { id: string; message: string } = message - try { - const signatureString = await signer.signMessage(request.message) - const eSig = splitSignature(signatureString) - const r = hexToBytes(eSig.r) - const s = hexToBytes(eSig.s) - const sigBytes = new Uint8Array(65) - sigBytes.set(r) - sigBytes.set(s, r.length) - sigBytes[64] = eSig.recoveryParam - - const signature = Buffer.from(sigBytes).toString('base64') - - await XMTPModule.receiveSignature(request.id, signature) - } catch (e) { - const errorMessage = 'ERROR in create. User rejected signature' - console.info(errorMessage, e) - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) - reject(errorMessage) - } - } - ) - - this.authSubscription = XMTPModule.emitter.addListener( - 'authed', - async (message: { - inboxId: string - address: string - installationId: string - dbPath: string - }) => { - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) - resolve( - new Client( - message.address, - message.inboxId as InboxId, - message.installationId, - message.dbPath, - options.codecs || [] - ) - ) - } - ) - await XMTPModule.auth( - await signer.getAddress(), - options.env, - options.appVersion, - Boolean(createSubscription), - Boolean(enableSubscription), - Boolean(authInboxSubscription), - Boolean(options.enableV3), - options.dbEncryptionKey, - options.dbDirectory, - options.historySyncUrl - ) - })().catch((error) => { - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) - console.error('ERROR in create: ', error) - }) - }) - } - static async exportNativeLogs() { return XMTPModule.exportNativeLogs() } private static removeAllSubscriptions( - createSubscription?: Subscription, - enableSubscription?: Subscription, authInboxSubscription?: Subscription ): void { ;[ - createSubscription, - enableSubscription, authInboxSubscription, this.signSubscription, this.authSubscription, @@ -172,192 +59,24 @@ export class Client< /** * Creates a new instance of the XMTP Client with a randomly generated address. * - * @param {Partial} opts - Optional configuration options for the Client. + * @param {Partial} opts - Configuration options for the Client. Must include encryption key. * @returns {Promise} A Promise that resolves to a new Client instance with a random address. */ static async createRandom( options: ClientOptions & { codecs?: ContentTypes } ): Promise> { - if ( - options.enableV3 === true && - (options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32) - ) { + if (options.dbEncryptionKey.length !== 32) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const { createSubscription, enableSubscription, authInboxSubscription } = - this.setupSubscriptions(options) + const { authInboxSubscription } = this.setupSubscriptions(options) const client = await XMTPModule.createRandom( options.env, - options.appVersion, - Boolean(createSubscription), - Boolean(enableSubscription), - Boolean(authInboxSubscription), - Boolean(options.enableV3), options.dbEncryptionKey, - options.dbDirectory, - options.historySyncUrl - ) - this.removeSubscription(createSubscription) - this.removeSubscription(enableSubscription) - this.removeSubscription(authInboxSubscription) - - return new Client( - client['address'], - client['inboxId'], - client['installationId'], - client['dbPath'], - options?.codecs || [] - ) - } - - /** - * Creates a new instance of the Client class from a provided key bundle. - * - * This method is useful for scenarios where you want to manually handle private key storage, - * allowing the application to have access to XMTP keys without exposing wallet keys. - * - * @param {string} keyBundle - The key bundle used for address generation. - * @param {Partial} opts - Optional configuration options for the Client. - * @returns {Promise} A Promise that resolves to a new Client instance based on the provided key bundle. - */ - static async createFromKeyBundle< - ContentCodecs extends DefaultContentTypes = [], - >( - keyBundle: string, - options: ClientOptions & { codecs?: ContentCodecs }, - wallet?: Signer | WalletClient | undefined - ): Promise> { - if ( - options.enableV3 === true && - (options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32) - ) { - throw new Error('Must pass an encryption key that is exactly 32 bytes.') - } - - if (!wallet) { - const client = await XMTPModule.createFromKeyBundle( - keyBundle, - options.env, - options.appVersion, - Boolean(options.enableV3), - options.dbEncryptionKey, - options.dbDirectory, - options.historySyncUrl - ) - - return new Client( - client['address'], - client['inboxId'], - client['installationId'], - client['dbPath'], - options.codecs || [] - ) - } else { - const signer = getSigner(wallet) - if (!signer) { - throw new Error('Signer is not configured') - } - return new Promise>((resolve, reject) => { - ;(async () => { - this.signSubscription = XMTPModule.emitter.addListener( - 'sign', - async (message: { id: string; message: string }) => { - const request: { id: string; message: string } = message - try { - const signatureString = await signer.signMessage( - request.message - ) - const eSig = splitSignature(signatureString) - const r = hexToBytes(eSig.r) - const s = hexToBytes(eSig.s) - const sigBytes = new Uint8Array(65) - sigBytes.set(r) - sigBytes.set(s, r.length) - sigBytes[64] = eSig.recoveryParam - - const signature = Buffer.from(sigBytes).toString('base64') - - await XMTPModule.receiveSignature(request.id, signature) - } catch (e) { - this.removeAllSubscriptions() - const errorMessage = 'ERROR in create. User rejected signature' - console.info(errorMessage, e) - reject(errorMessage) - } - } - ) - - this.authSubscription = XMTPModule.emitter.addListener( - 'bundleAuthed', - async (message: { - inboxId: string - address: string - installationId: string - dbPath: string - }) => { - this.removeAllSubscriptions() - resolve( - new Client( - message.address, - message.inboxId as InboxId, - message.installationId, - message.dbPath, - options.codecs || [] - ) - ) - } - ) - await XMTPModule.createFromKeyBundleWithSigner( - await signer.getAddress(), - keyBundle, - options.env, - options.appVersion, - Boolean(options.enableV3), - options.dbEncryptionKey, - options.dbDirectory, - options.historySyncUrl - ) - })().catch((error) => { - this.removeAllSubscriptions() - console.error('ERROR in create: ', error) - }) - }) - } - } - - /** - * Creates a new V3 ONLY instance of the XMTP Client with a randomly generated address. - * - * @param {Partial} opts - Configuration options for the Client. Must include encryption key. - * @returns {Promise} A Promise that resolves to a new V3 ONLY Client instance with a random address. - */ - static async createRandomV3( - options: ClientOptions & { codecs?: ContentTypes } - ): Promise> { - options.enableV3 = true - if ( - options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32 - ) { - throw new Error('Must pass an encryption key that is exactly 32 bytes.') - } - const { createSubscription, enableSubscription, authInboxSubscription } = - this.setupSubscriptions(options) - const client = await XMTPModule.createRandomV3( - options.env, options.appVersion, - Boolean(createSubscription), - Boolean(enableSubscription), Boolean(authInboxSubscription), - Boolean(options.enableV3), - options.dbEncryptionKey, options.dbDirectory, options.historySyncUrl ) - this.removeSubscription(createSubscription) - this.removeSubscription(enableSubscription) this.removeSubscription(authInboxSubscription) return new Client( @@ -370,29 +89,24 @@ export class Client< } /** - * Creates a new V3 ONLY instance of the Client class using the provided signer. + * Creates a new instance of the Client class using the provided signer. * * @param {Signer} signer - The signer object used for authentication and message signing. * @param {Partial} opts - Configuration options for the Client. Must include an encryption key. - * @returns {Promise} A Promise that resolves to a new V3 ONLY Client instance. + * @returns {Promise} A Promise that resolves to a new Client instance. * * See {@link https://xmtp.org/docs/build/authentication#create-a-client | XMTP Docs} for more information. */ - static async createV3< + static async create< ContentCodecs extends DefaultContentTypes = DefaultContentTypes, >( wallet: Signer | WalletClient | null, options: ClientOptions & { codecs?: ContentCodecs } ): Promise> { - options.enableV3 = true - if ( - options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32 - ) { + if (options.dbEncryptionKey.length !== 32) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const { enableSubscription, createSubscription, authInboxSubscription } = - this.setupSubscriptions(options) + const { authInboxSubscription } = this.setupSubscriptions(options) const signer = getSigner(wallet) if (!signer) { throw new Error('Signer is not configured') @@ -426,29 +140,21 @@ export class Client< } catch (e) { const errorMessage = 'ERROR in create. User rejected signature' console.info(errorMessage, e) - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) + this.removeAllSubscriptions(authInboxSubscription) reject(errorMessage) } } ) this.authSubscription = XMTPModule.emitter.addListener( - 'authedV3', + 'authed', async (message: { inboxId: string address: string installationId: string dbPath: string }) => { - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) + this.removeAllSubscriptions(authInboxSubscription) resolve( new Client( message.address, @@ -460,15 +166,12 @@ export class Client< ) } ) - await XMTPModule.createV3( + await XMTPModule.create( await signer.getAddress(), options.env, + options.dbEncryptionKey, options.appVersion, - Boolean(createSubscription), - Boolean(enableSubscription), Boolean(authInboxSubscription), - Boolean(options.enableV3), - options.dbEncryptionKey, options.dbDirectory, options.historySyncUrl, signer.walletType(), @@ -476,44 +179,35 @@ export class Client< signer.getBlockNumber() ) })().catch((error) => { - this.removeAllSubscriptions( - createSubscription, - enableSubscription, - authInboxSubscription - ) + this.removeAllSubscriptions(authInboxSubscription) console.error('ERROR in create: ', error) }) }) } /** - * Builds a V3 ONLY instance of the Client class using the provided address and chainId if SCW. + * Builds a instance of the Client class using the provided address and chainId if SCW. * * @param {string} address - The address of the account to build * @param {Partial} opts - Configuration options for the Client. Must include an encryption key. - * @returns {Promise} A Promise that resolves to a new V3 ONLY Client instance. + * @returns {Promise} A Promise that resolves to a new Client instance. * * See {@link https://xmtp.org/docs/build/authentication#create-a-client | XMTP Docs} for more information. */ - static async buildV3< + static async build< ContentCodecs extends DefaultContentTypes = DefaultContentTypes, >( - address: string, + address: Address, options: ClientOptions & { codecs?: ContentCodecs } ): Promise> { - options.enableV3 = true - if ( - options.dbEncryptionKey === undefined || - options.dbEncryptionKey.length !== 32 - ) { + if (options.dbEncryptionKey.length !== 32) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const client = await XMTPModule.buildV3( + const client = await XMTPModule.build( address, options.env, - options.appVersion, - Boolean(options.enableV3), options.dbEncryptionKey, + options.appVersion, options.dbDirectory, options.historySyncUrl ) @@ -530,31 +224,10 @@ export class Client< /** * Drop the client from memory. Use when you want to remove the client from memory and are done with it. */ - static async dropClient(inboxId: string) { + static async dropClient(inboxId: InboxId) { return await XMTPModule.dropClient(inboxId) } - /** - * Static method to determine if the address is currently in our network. - * - * This method checks if the specified peer has signed up for XMTP. - * - * @param {string} peerAddress - The address of the peer to check for messaging eligibility. - * @param {Partial} opts - Optional configuration options for the Client. - * @returns {Promise} - */ - static async canMessage( - peerAddress: string, - opts?: Partial - ): Promise { - const options = defaultOptions(opts) - return await XMTPModule.staticCanMessage( - peerAddress, - options.env, - options.appVersion - ) - } - private static addSubscription( event: string, opts: ClientOptions, @@ -583,28 +256,8 @@ export class Client< } private static setupSubscriptions(opts: ClientOptions): { - createSubscription?: Subscription - enableSubscription?: Subscription authInboxSubscription?: Subscription } { - const enableSubscription = this.addSubscription( - 'preEnableIdentityCallback', - opts, - async () => { - await this.executeCallback(opts?.preEnableIdentityCallback) - XMTPModule.preEnableIdentityCallbackCompleted() - } - ) - - const createSubscription = this.addSubscription( - 'preCreateIdentityCallback', - opts, - async () => { - await this.executeCallback(opts?.preCreateIdentityCallback) - XMTPModule.preCreateIdentityCallbackCompleted() - } - ) - const authInboxSubscription = this.addSubscription( 'preAuthenticateToInboxCallback', opts, @@ -614,26 +267,25 @@ export class Client< } ) - return { createSubscription, enableSubscription, authInboxSubscription } + return { authInboxSubscription } } /** * Static method to determine the inboxId for the address. * - * @param {string} peerAddress - The address of the peer to check for messaging eligibility. + * @param {Address} peerAddress - The address of the peer to check for messaging eligibility. * @param {Partial} opts - Optional configuration options for the Client. * @returns {Promise} */ static async getOrCreateInboxId( - address: string, - opts?: Partial + address: Address, + env: XMTPEnvironment ): Promise { - const options = defaultOptions(opts) - return await XMTPModule.getOrCreateInboxId(address, options.env) + return await XMTPModule.getOrCreateInboxId(address, env) } constructor( - address: string, + address: Address, inboxId: InboxId, installationId: string, dbPath: string, @@ -644,7 +296,7 @@ export class Client< this.installationId = installationId this.dbPath = dbPath this.conversations = new Conversations(this) - this.contacts = new Contacts(this) + this.preferences = new PrivatePreferences(this) this.codecRegistry = {} this.register(new TextCodec()) @@ -659,52 +311,14 @@ export class Client< this.codecRegistry[id] = contentCodec } - async sign(digest: Uint8Array, keyType: KeyType): Promise { - return XMTPModule.sign( - this.inboxId, - digest, - keyType.kind, - keyType.prekeyIndex - ) - } - - async exportPublicKeyBundle(): Promise { - return XMTPModule.exportPublicKeyBundle(this.inboxId) - } - - /** - * Exports the key bundle associated with the current XMTP address. - * - * This method allows you to obtain the unencrypted key bundle for the current XMTP address. - * Ensure the exported keys are stored securely and encrypted. - * - * @returns {Promise} A Promise that resolves to the unencrypted key bundle for the current XMTP address. - */ - async exportKeyBundle(): Promise { - return XMTPModule.exportKeyBundle(this.inboxId) - } - /** - * Determines whether the current user can send messages to a specified peer over 1:1 conversations. - * - * This method checks if the specified peer has signed up for XMTP - * and ensures that the message is not addressed to the sender (no self-messaging). - * - * @param {string} peerAddress - The address of the peer to check for messaging eligibility. - * @returns {Promise} A Promise resolving to true if messaging is allowed, and false otherwise. - */ - async canMessage(peerAddress: string): Promise { - return await XMTPModule.canMessage(this.inboxId, peerAddress) - } - - /** - * Find the inboxId associated with this address + * Find the Address associated with this address * * @param {string} peerAddress - The address of the peer to check for inboxId. * @returns {Promise} A Promise resolving to the InboxId. */ async findInboxIdFromAddress( - peerAddress: string + peerAddress: Address ): Promise { return await XMTPModule.findInboxIdFromAddress(this.inboxId, peerAddress) } @@ -798,38 +412,11 @@ export class Client< * * This method checks if the specified peers are using clients that support group messaging. * - * @param {string[]} addresses - The addresses of the peers to check for messaging eligibility. - * @returns {Promise<{ [key: string]: boolean }>} A Promise resolving to a hash of addresses and booleans if they can message on the V3 network. - */ - async canGroupMessage( - addresses: string[] - ): Promise<{ [key: string]: boolean }> { - return await XMTPModule.canGroupMessage(this.inboxId, addresses) - } - - // TODO: support persisting conversations for quick lookup - // async importConversation(exported: string): Promise { ... } - // async exportConversation(topic: string): Promise { ... } - - /** - * Retrieves a list of batch messages based on the provided queries. - * - * This method pulls messages associated from multiple conversation with the current address - * and specified queries. - * - * @param {Query[]} queries - An array of queries to filter the batch messages. - * @returns {Promise} A Promise that resolves to a list of batch messages. - * @throws {Error} The error is logged, and the method gracefully returns an empty array. + * @param {Address[]} addresses - The addresses of the peers to check for messaging eligibility. + * @returns {Promise<{ [key: Address]: boolean }>} A Promise resolving to a hash of addresses and booleans if they can message on the V3 network. */ - async listBatchMessages( - queries: Query[] - ): Promise[]> { - try { - return await XMTPModule.listBatchMessages(this, queries) - } catch (e) { - console.info('ERROR in listBatchMessages', e) - return [] - } + async canMessage(addresses: Address[]): Promise<{ [key: Address]: boolean }> { + return await XMTPModule.canMessage(this.inboxId, addresses) } /** @@ -847,7 +434,7 @@ export class Client< if (!file.fileUri?.startsWith('file://')) { throw new Error('the attachment must be a local file:// uri') } - return await XMTPModule.encryptAttachment(this.inboxId, file) + return await XMTPModule.encryptAttachment(file) } /** @@ -864,23 +451,7 @@ export class Client< if (!encryptedFile.encryptedLocalFileUri?.startsWith('file://')) { throw new Error('the attachment must be a local file:// uri') } - return await XMTPModule.decryptAttachment(this.inboxId, encryptedFile) - } - - /** - * Sends a prepared message. - * - * @param {PreparedLocalMessage} prepared - The prepared local message to be sent. - * @returns {Promise} A Promise that resolves to a string identifier for the sent message. - * @throws {Error} Throws an error if there is an issue with sending the prepared message. - */ - async sendPreparedMessage(prepared: PreparedLocalMessage): Promise { - try { - return await XMTPModule.sendPreparedMessage(this.inboxId, prepared) - } catch (e) { - console.info('ERROR in sendPreparedMessage()', e) - throw e - } + return await XMTPModule.decryptAttachment(encryptedFile) } } export type XMTPEnvironment = 'local' | 'dev' | 'production' @@ -923,20 +494,3 @@ export type KeyType = { kind: 'identity' | 'prekey' prekeyIndex?: number } - -/** - * Provide a default client configuration. These settings can be used on their own, or as a starting point for custom configurations - * - * @param opts additional options to override the default settings - */ -export function defaultOptions(opts?: Partial): ClientOptions { - const _defaultOptions: ClientOptions = { - env: 'dev', - enableV3: false, - dbEncryptionKey: undefined, - dbDirectory: undefined, - historySyncUrl: undefined, - } - - return { ..._defaultOptions, ...opts } as ClientOptions -} diff --git a/src/lib/Contacts.ts b/src/lib/Contacts.ts deleted file mode 100644 index 97d8b9a12..000000000 --- a/src/lib/Contacts.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Client, InboxId } from './Client' -import { ConsentListEntry } from './ConsentListEntry' -import * as XMTPModule from '../index' -import { getAddress } from '../utils/address' - -export default class Contacts { - client: Client - - constructor(client: Client) { - this.client = client - } - - async isAllowed(address: string): Promise { - return await XMTPModule.isAllowed(this.client.inboxId, getAddress(address)) - } - - async isDenied(address: string): Promise { - return await XMTPModule.isDenied(this.client.inboxId, getAddress(address)) - } - - async deny(addresses: string[]): Promise { - const checkSummedAddresses = addresses.map((address) => getAddress(address)) - return await XMTPModule.denyContacts( - this.client.inboxId, - checkSummedAddresses - ) - } - - async allow(addresses: string[]): Promise { - const checkSummedAddresses = addresses.map((address) => getAddress(address)) - return await XMTPModule.allowContacts( - this.client.inboxId, - checkSummedAddresses - ) - } - - async refreshConsentList(): Promise { - return await XMTPModule.refreshConsentList(this.client.inboxId) - } - - async consentList(): Promise { - return await XMTPModule.consentList(this.client.inboxId) - } - - async allowGroups(groupIds: string[]): Promise { - return await XMTPModule.allowGroups(this.client.inboxId, groupIds) - } - - async denyGroups(groupIds: string[]): Promise { - return await XMTPModule.denyGroups(this.client.inboxId, groupIds) - } - - async isGroupAllowed(groupId: string): Promise { - return await XMTPModule.isGroupAllowed(this.client.inboxId, groupId) - } - - async isGroupDenied(groupId: string): Promise { - return await XMTPModule.isGroupDenied(this.client.inboxId, groupId) - } - - async allowInboxes(inboxIds: InboxId[]): Promise { - return await XMTPModule.allowInboxes(this.client.inboxId, inboxIds) - } - - async denyInboxes(inboxIds: InboxId[]): Promise { - return await XMTPModule.denyInboxes(this.client.inboxId, inboxIds) - } - - async isInboxAllowed(inboxId: InboxId): Promise { - return await XMTPModule.isInboxAllowed(this.client.inboxId, inboxId) - } - - async isInboxDenied(inboxId: InboxId): Promise { - return await XMTPModule.isInboxDenied(this.client.inboxId, inboxId) - } -} diff --git a/src/lib/Conversation.ts b/src/lib/Conversation.ts index e4770db4e..1e9c5626a 100644 --- a/src/lib/Conversation.ts +++ b/src/lib/Conversation.ts @@ -2,7 +2,7 @@ import { ConsentState } from './ConsentListEntry' import { ConversationSendPayload, MessagesOptions } from './types' import { DefaultContentTypes } from './types/DefaultContentType' import * as XMTP from '../index' -import { Conversation, DecodedMessage, Member, Dm, Group } from '../index' +import { DecodedMessage, Member, Dm, Group } from '../index' export enum ConversationVersion { GROUP = 'GROUP', @@ -36,4 +36,4 @@ export interface ConversationBase { export type Conversation< ContentTypes extends DefaultContentTypes = DefaultContentTypes, -> = Group | Dm +> = Group | Dm \ No newline at end of file diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index c1794ed81..4bdfeee20 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -1,20 +1,25 @@ -import type { invitation, keystore } from '@xmtp/proto' - import { Client } from './Client' -import { - ConversationVersion, - ConversationContainer, -} from './Conversation' +import { ConversationVersion } from './Conversation' import { DecodedMessage } from './DecodedMessage' import { Dm, DmParams } from './Dm' import { Group, GroupParams } from './Group' -import { Member } from './Member' +import { + ConversationOrder, + ConversationOptions, +} from './types/ConversationOptions' import { CreateGroupOptions } from './types/CreateGroupOptions' import { EventTypes } from './types/EventTypes' -import { ConversationOrder, GroupOptions } from './types/ConversationOptions' import { PermissionPolicySet } from './types/PermissionPolicySet' import * as XMTPModule from '../index' -import { ContentCodec } from '../index' +import { + Address, + ContentCodec, + Conversation, + ConversationId, + ConversationTopic, + ConversationType, + MessageId, +} from '../index' import { getAddress } from '../utils/address' export default class Conversations< @@ -32,50 +37,60 @@ export default class Conversations< * * This method creates a new conversation with the specified peer address and context. * - * @param {string} peerAddress - The address of the peer to create a conversation with. + * @param {Address} peerAddress - The address of the peer to create a conversation with. * @returns {Promise} A Promise that resolves to a Conversation object. */ - async newConversation(peerAddress: string): Promise> { + async newConversation( + peerAddress: Address + ): Promise> { const checksumAddress = getAddress(peerAddress) - return await XMTPModule.createConversation( - this.client, - checksumAddress, - ) + return await XMTPModule.findOrCreateDm(this.client, checksumAddress) } /** - * Creates a new V3 conversation. + * Creates a new conversation. * * This method creates a new conversation with the specified peer address. * - * @param {string} peerAddress - The address of the peer to create a conversation with. + * @param {Address} peerAddress - The address of the peer to create a conversation with. * @returns {Promise} A Promise that resolves to a Dm object. */ - async findOrCreateDm(peerAddress: string): Promise> { + async findOrCreateDm(peerAddress: Address): Promise> { return await XMTPModule.findOrCreateDm(this.client, peerAddress) } /** * This method returns a list of all groups that the client is a member of. * To get the latest list of groups from the network, call syncGroups() first. - * @param {GroupOptions} opts - The options to specify what fields you want returned for the groups in the list. + * @param {ConversationOptions} opts - The options to specify what fields you want returned for the groups in the list. * @param {ConversationOrder} order - The order to specify if you want groups listed by last message or by created at. * @param {number} limit - Limit the number of groups returned in the list. * * @returns {Promise} A Promise that resolves to an array of Group objects. */ async listGroups( - opts?: GroupOptions | undefined, + opts?: ConversationOptions | undefined, order?: ConversationOrder | undefined, limit?: number | undefined ): Promise[]> { - const result = await XMTPModule.listGroups(this.client, opts, order, limit) - - for (const group of result) { - this.known[group.id] = true - } + return await XMTPModule.listGroups(this.client, opts, order, limit) + } - return result + /** + * This method returns a list of all groups that the client is a member of. + * To get the latest list of groups from the network, call syncGroups() first. + * @param {ConversationOptions} opts - The options to specify what fields you want returned for the groups in the list. + * @param {ConversationOrder} order - The order to specify if you want groups listed by last message or by created at. + * @param {number} limit - Limit the number of groups returned in the list. + * + * @returns {Promise} A Promise that resolves to an array of Group objects. + */ + async listDms( + opts?: ConversationOptions | undefined, + order?: ConversationOrder | undefined, + limit?: number | undefined + ): Promise[]> { + return await XMTPModule.listDms(this.client, opts, order, limit) } /** @@ -84,7 +99,9 @@ export default class Conversations< * * @returns {Promise} A Promise that resolves to a Group or undefined if not found. */ - async findGroup(groupId: string): Promise | undefined> { + async findGroup( + groupId: ConversationId + ): Promise | undefined> { return await XMTPModule.findGroup(this.client, groupId) } @@ -94,7 +111,7 @@ export default class Conversations< * * @returns {Promise} A Promise that resolves to a Group or undefined if not found. */ - async findDm(address: string): Promise | undefined> { + async findDm(address: Address): Promise | undefined> { return await XMTPModule.findDm(this.client, address) } @@ -102,11 +119,11 @@ export default class Conversations< * This method returns a conversation by the topic if that conversation exists in the local database. * To get the latest list of groups from the network, call syncConversations() first. * - * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + * @returns {Promise} A Promise that resolves to a Group or undefined if not found. */ async findConversationByTopic( - topic: string - ): Promise | undefined> { + topic: ConversationTopic + ): Promise | undefined> { return await XMTPModule.findConversationByTopic(this.client, topic) } @@ -114,11 +131,11 @@ export default class Conversations< * This method returns a conversation by the conversation id if that conversation exists in the local database. * To get the latest list of groups from the network, call syncConversations() first. * - * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + * @returns {Promise} A Promise that resolves to a Group or undefined if not found. */ async findConversation( - conversationId: string - ): Promise | undefined> { + conversationId: ConversationId + ): Promise | undefined> { return await XMTPModule.findConversation(this.client, conversationId) } @@ -128,93 +145,48 @@ export default class Conversations< * * @returns {Promise} A Promise that resolves to a DecodedMessage or undefined if not found. */ - async findV3Message( - messageId: string + async findMessage( + messageId: MessageId ): Promise | undefined> { - return await XMTPModule.findV3Message(this.client, messageId) - } - - /** - * This method returns a list of all conversations and groups that the client is a member of. - * To include the latest groups from the network in the returned list, call syncGroups() first. - * - * @returns {Promise} A Promise that resolves to an array of ConversationContainer objects. - */ - async listAll(): Promise[]> { - const result = await XMTPModule.listAll(this.client) - - for (const conversationContainer of result) { - this.known[conversationContainer.topic] = true - } - - return result + return await XMTPModule.findMessage(this.client, messageId) } /** * This method returns a list of all V3 conversations that the client is a member of. * To include the latest groups from the network in the returned list, call syncGroups() first. * - * @returns {Promise} A Promise that resolves to an array of ConversationContainer objects. + * @returns {Promise} A Promise that resolves to an array of Conversation objects. */ - async listConversations( - opts?: GroupOptions | undefined, + async list( + opts?: ConversationOptions | undefined, order?: ConversationOrder | undefined, limit?: number | undefined - ): Promise[]> { - return await XMTPModule.listV3Conversations(this.client, opts, order, limit) + ): Promise[]> { + return await XMTPModule.listConversations(this.client, opts, order, limit) } /** - * This method streams groups that the client is a member of. - * - * @returns {Promise} A Promise that resolves to an array of Group objects. + * This method streams conversations that the client is a member of. + * @param {type} ConversationType - Whether to stream groups, dms, or both + * @returns {Promise} A Promise that resolves to an array of Conversation objects. */ - async streamGroups( - callback: (group: Group) => Promise - ): Promise<() => void> { - XMTPModule.subscribeToGroups(this.client.inboxId) - const groupsSubscription = XMTPModule.emitter.addListener( - EventTypes.Group, - async ({ inboxId, group }: { inboxId: string; group: GroupParams }) => { - if (this.client.inboxId !== inboxId) { - return - } - this.known[group.id] = true - await callback(new Group(this.client, group)) - } - ) - this.subscriptions[EventTypes.Group] = groupsSubscription - return () => { - groupsSubscription.remove() - XMTPModule.unsubscribeFromGroups(this.client.inboxId) - } - } - - /** - * This method streams V3 conversations that the client is a member of. - * - * @returns {Promise} A Promise that resolves to an array of ConversationContainer objects. - */ - async streamConversations( - callback: ( - conversation: ConversationContainer - ) => Promise + async stream( + callback: (conversation: Conversation) => Promise, + type: ConversationType = 'all' ): Promise<() => void> { - XMTPModule.subscribeToV3Conversations(this.client.inboxId) + XMTPModule.subscribeToConversations(this.client.inboxId, type) const subscription = XMTPModule.emitter.addListener( - EventTypes.ConversationV3, + EventTypes.Conversation, async ({ inboxId, conversation, }: { inboxId: string - conversation: ConversationContainer + conversation: Conversation }) => { if (inboxId !== this.client.inboxId) { return } - - this.known[conversation.topic] = true if (conversation.version === ConversationVersion.GROUP) { return await callback( new Group(this.client, conversation as unknown as GroupParams) @@ -228,7 +200,7 @@ export default class Conversations< ) return () => { subscription.remove() - XMTPModule.unsubscribeFromV3Conversations(this.client.inboxId) + XMTPModule.unsubscribeFromConversations(this.client.inboxId) } } @@ -237,12 +209,12 @@ export default class Conversations< * * This method creates a new group with the specified peer addresses and options. * - * @param {string[]} peerAddresses - The addresses of the peers to create a group with. + * @param {Address[]} peerAddresses - The addresses of the peers to create a group with. * @param {CreateGroupOptions} opts - The options to use for the group. * @returns {Promise>} A Promise that resolves to a Group object. */ async newGroup( - peerAddresses: string[], + peerAddresses: Address[], opts?: CreateGroupOptions | undefined ): Promise> { return await XMTPModule.createGroup( @@ -261,13 +233,13 @@ export default class Conversations< * * This method creates a new group with the specified peer addresses and options. * - * @param {string[]} peerAddresses - The addresses of the peers to create a group with. + * @param {Address[]} peerAddresses - The addresses of the peers to create a group with. * @param {PermissionPolicySet} permissionPolicySet - The permission policy set to use for the group. * @param {CreateGroupOptions} opts - The options to use for the group. * @returns {Promise>} A Promise that resolves to a Group object. */ async newGroupCustomPermissions( - peerAddresses: string[], + peerAddresses: Address[], permissionPolicySet: PermissionPolicySet, opts?: CreateGroupOptions | undefined ): Promise> { @@ -283,134 +255,35 @@ export default class Conversations< } /** - * Executes a network request to fetch the latest list of groups associated with the client + * Executes a network request to fetch the latest list of conversations associated with the client * and save them to the local state. */ - async syncGroups() { - await XMTPModule.syncConversations(this.client.inboxId) - } - async syncConversations() { await XMTPModule.syncConversations(this.client.inboxId) } /** - * Executes a network request to sync all active groups associated with the client + * Executes a network request to sync all active conversations associated with the client * - * @returns {Promise} A Promise that resolves to the number of groups synced. + * @returns {Promise} A Promise that resolves to the number of conversations synced. */ - async syncAllGroups(): Promise { - return await XMTPModule.syncAllConversations(this.client.inboxId) - } - async syncAllConversations(): Promise { return await XMTPModule.syncAllConversations(this.client.inboxId) } - /** - * Sets up a real-time stream to listen for new conversations being started. - * - * This method subscribes to conversations in real-time and listens for incoming conversation events. - * When a new conversation is detected, the provided callback function is invoked with the details of the conversation. - * @param {Function} callback - A callback function that will be invoked with the new Conversation when a conversation is started. - * @returns {Promise} A Promise that resolves when the stream is set up. - * @warning This stream will continue infinitely. To end the stream, you can call {@linkcode Conversations.cancelStream | cancelStream()}. - */ - async stream( - callback: (conversation: Conversation) => Promise - ) { - XMTPModule.subscribeToConversations(this.client.inboxId) - const subscription = XMTPModule.emitter.addListener( - EventTypes.Conversation, - async ({ - inboxId, - conversation, - }: { - inboxId: string - conversation: ConversationParams - }) => { - if (inboxId !== this.client.inboxId) { - return - } - if (this.known[conversation.topic]) { - return - } - - this.known[conversation.topic] = true - await callback(new Conversation(this.client, conversation)) - } - ) - this.subscriptions[EventTypes.Conversation] = subscription - } - - /** - * Sets up a real-time stream to listen for new conversations and groups being started. - * - * This method subscribes to conversations in real-time and listens for incoming conversation and group events. - * When a new conversation is detected, the provided callback function is invoked with the details of the conversation. - * @param {Function} callback - A callback function that will be invoked with the new Conversation when a conversation is started. - * @returns {Promise} A Promise that resolves when the stream is set up. - * @warning This stream will continue infinitely. To end the stream, you can call the function returned by this streamAll. - */ - async streamAll( - callback: ( - conversation: ConversationContainer - ) => Promise - ) { - XMTPModule.subscribeToAll(this.client.inboxId) - const subscription = XMTPModule.emitter.addListener( - EventTypes.ConversationContainer, - async ({ - inboxId, - conversationContainer, - }: { - inboxId: string - conversationContainer: ConversationContainer - }) => { - if (inboxId !== this.client.inboxId) { - return - } - if (this.known[conversationContainer.topic]) { - return - } - - this.known[conversationContainer.topic] = true - if (conversationContainer.version === ConversationVersion.GROUP) { - return await callback( - new Group( - this.client, - conversationContainer as unknown as GroupParams, - ) - ) - } else { - return await callback( - new Conversation( - this.client, - conversationContainer as ConversationParams - ) - ) - } - } - ) - return () => { - subscription.remove() - this.cancelStream() - } - } - /** * Listen for new messages in all conversations. * * This method subscribes to all conversations in real-time and listens for incoming and outgoing messages. - * @param {boolean} includeGroups - Whether or not to include group messages in the stream. + * @param {type} ConversationType - Whether to stream messages from groups, dms, or both * @param {Function} callback - A callback function that will be invoked when a message is sent or received. * @returns {Promise} A Promise that resolves when the stream is set up. */ async streamAllMessages( callback: (message: DecodedMessage) => Promise, - includeGroups: boolean = false + type: ConversationType = 'all' ): Promise { - XMTPModule.subscribeToAllMessages(this.client.inboxId, includeGroups) + XMTPModule.subscribeToAllMessages(this.client.inboxId, type) const subscription = XMTPModule.emitter.addListener( EventTypes.Message, async ({ @@ -423,102 +296,17 @@ export default class Conversations< if (inboxId !== this.client.inboxId) { return } - if (this.known[message.id]) { - return - } - - this.known[message.id] = true await callback(DecodedMessage.fromObject(message, this.client)) } ) this.subscriptions[EventTypes.Message] = subscription } - /** - * Listen for new messages in all groups. - * - * This method subscribes to all groups in real-time and listens for incoming and outgoing messages. - * @param {Function} callback - A callback function that will be invoked when a message is sent or received. - * @returns {Promise} A Promise that resolves when the stream is set up. - */ - async streamAllGroupMessages( - callback: (message: DecodedMessage) => Promise - ): Promise { - XMTPModule.subscribeToAllGroupMessages(this.client.inboxId) - const subscription = XMTPModule.emitter.addListener( - EventTypes.AllGroupMessage, - async ({ - inboxId, - message, - }: { - inboxId: string - message: DecodedMessage - }) => { - if (inboxId !== this.client.inboxId) { - return - } - if (this.known[message.id]) { - return - } - - this.known[message.id] = true - await callback(DecodedMessage.fromObject(message, this.client)) - } - ) - this.subscriptions[EventTypes.AllGroupMessage] = subscription - } - - /** - * Listen for new messages in all v3 conversations. - * - * This method subscribes to all groups in real-time and listens for incoming and outgoing messages. - * @param {Function} callback - A callback function that will be invoked when a message is sent or received. - * @returns {Promise} A Promise that resolves when the stream is set up. - */ - async streamAllConversationMessages( - callback: (message: DecodedMessage) => Promise - ): Promise { - XMTPModule.subscribeToAllConversationMessages(this.client.inboxId) - const subscription = XMTPModule.emitter.addListener( - EventTypes.AllConversationMessages, - async ({ - inboxId, - message, - }: { - inboxId: string - message: DecodedMessage - }) => { - if (inboxId !== this.client.inboxId) { - return - } - if (this.known[message.id]) { - return - } - - this.known[message.id] = true - await callback(DecodedMessage.fromObject(message, this.client)) - } - ) - this.subscriptions[EventTypes.AllConversationMessages] = subscription - } - - async fromWelcome(encryptedMessage: string): Promise> { - try { - return await XMTPModule.processWelcomeMessage( - this.client, - encryptedMessage - ) - } catch (e) { - console.info('ERROR in processWelcomeMessage()', e) - throw e - } - } - - async conversationFromWelcome( + async fromWelcome( encryptedMessage: string - ): Promise> { + ): Promise> { try { - return await XMTPModule.processConversationWelcomeMessage( + return await XMTPModule.processWelcomeMessage( this.client, encryptedMessage ) @@ -539,25 +327,6 @@ export default class Conversations< XMTPModule.unsubscribeFromConversations(this.client.inboxId) } - /** - * Cancels the stream for new conversations. - */ - cancelStreamGroups() { - if (this.subscriptions[EventTypes.Group]) { - this.subscriptions[EventTypes.Group].remove() - delete this.subscriptions[EventTypes.Group] - } - XMTPModule.unsubscribeFromGroups(this.client.inboxId) - } - - cancelStreamConversations() { - if (this.subscriptions[EventTypes.ConversationV3]) { - this.subscriptions[EventTypes.ConversationV3].remove() - delete this.subscriptions[EventTypes.ConversationV3] - } - XMTPModule.unsubscribeFromV3Conversations(this.client.inboxId) - } - /** * Cancels the stream for new messages in all conversations. */ @@ -568,23 +337,4 @@ export default class Conversations< } XMTPModule.unsubscribeFromAllMessages(this.client.inboxId) } - - /** - * Cancels the stream for new messages in all groups. - */ - cancelStreamAllGroupMessages() { - if (this.subscriptions[EventTypes.AllGroupMessage]) { - this.subscriptions[EventTypes.AllGroupMessage].remove() - delete this.subscriptions[EventTypes.AllGroupMessage] - } - XMTPModule.unsubscribeFromAllGroupMessages(this.client.inboxId) - } - - cancelStreamAllConversations() { - if (this.subscriptions[EventTypes.AllConversationMessages]) { - this.subscriptions[EventTypes.AllConversationMessages].remove() - delete this.subscriptions[EventTypes.AllConversationMessages] - } - XMTPModule.unsubscribeFromAllConversationMessages(this.client.inboxId) - } } diff --git a/src/lib/DecodedMessage.ts b/src/lib/DecodedMessage.ts index 4eea4ad29..c8644f782 100644 --- a/src/lib/DecodedMessage.ts +++ b/src/lib/DecodedMessage.ts @@ -28,7 +28,7 @@ export class DecodedMessage< topic: string contentTypeId: string senderAddress: string - sent: number // timestamp in milliseconds + sentNs: number // timestamp in nanoseconds nativeContent: NativeMessageContent fallback: string | undefined deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED @@ -44,7 +44,7 @@ export class DecodedMessage< decoded.topic, decoded.contentTypeId, decoded.senderAddress, - decoded.sent, + decoded.sentNs, decoded.content, decoded.fallback, decoded.deliveryStatus @@ -59,7 +59,7 @@ export class DecodedMessage< topic: string contentTypeId: string senderAddress: string - sent: number // timestamp in milliseconds + sentNs: number // timestamp in nanoseconds content: any fallback: string | undefined deliveryStatus: MessageDeliveryStatus | undefined @@ -72,7 +72,7 @@ export class DecodedMessage< object.topic, object.contentTypeId, object.senderAddress, - object.sent, + object.sentNs, object.content, object.fallback, object.deliveryStatus @@ -85,7 +85,7 @@ export class DecodedMessage< topic: string, contentTypeId: string, senderAddress: string, - sent: number, + sentNs: number, content: any, fallback: string | undefined, deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED @@ -95,7 +95,7 @@ export class DecodedMessage< this.topic = topic this.contentTypeId = contentTypeId this.senderAddress = senderAddress - this.sent = sent + this.sentNs = sentNs this.nativeContent = content // undefined comes back as null when bridged, ensure undefined so integrators don't have to add a new check for null as well this.fallback = fallback ?? undefined diff --git a/src/lib/Dm.ts b/src/lib/Dm.ts index dfd7f3cf2..de0f8c218 100644 --- a/src/lib/Dm.ts +++ b/src/lib/Dm.ts @@ -1,9 +1,6 @@ import { InboxId } from './Client' import { ConsentState } from './ConsentListEntry' -import { - ConversationVersion, - ConversationBase, -} from './Conversation' +import { ConversationVersion, ConversationBase } from './Conversation' import { DecodedMessage } from './DecodedMessage' import { Member } from './Member' import { ConversationSendPayload } from './types/ConversationCodecs' @@ -11,11 +8,12 @@ import { DefaultContentTypes } from './types/DefaultContentType' import { EventTypes } from './types/EventTypes' import { MessagesOptions } from './types/MessagesOptions' import * as XMTP from '../index' +import { ConversationId, ConversationTopic } from '../index' export interface DmParams { - id: string + id: ConversationId createdAt: number - topic: string + topic: ConversationTopic consentState: ConsentState lastMessage?: DecodedMessage } @@ -24,7 +22,7 @@ export class Dm implements ConversationBase { client: XMTP.Client - id: string + id: ConversationId createdAt: number version = ConversationVersion.DM as const topic: string @@ -104,13 +102,9 @@ export class Dm content = { text: content } } - return await XMTP.prepareConversationMessage( - this.client.inboxId, - this.id, - content - ) + return await XMTP.prepareMessage(this.client.inboxId, this.id, content) } catch (e) { - console.info('ERROR in prepareGroupMessage()', e.message) + console.info('ERROR in prepareMessage()', e.message) throw e } } @@ -122,10 +116,7 @@ export class Dm */ async publishPreparedMessages() { try { - return await XMTP.publishPreparedGroupMessages( - this.client.inboxId, - this.id - ) + return await XMTP.publishPreparedMessages(this.client.inboxId, this.id) } catch (e) { console.info('ERROR in publishPreparedMessages()', e.message) throw e @@ -137,8 +128,8 @@ export class Dm * To get the latest messages from the network, call sync() first. * * @param {number | undefined} limit - Optional maximum number of messages to return. - * @param {number | Date | undefined} before - Optional filter for specifying the maximum timestamp of messages to return. - * @param {number | Date | undefined} after - Optional filter for specifying the minimum timestamp of messages to return. + * @param {number | undefined} before - Optional filter for specifying the maximum timestamp of messages to return. + * @param {number | undefined} after - Optional filter for specifying the minimum timestamp of messages to return. * @param direction - Optional parameter to specify the time ordering of the messages to return. * @returns {Promise[]>} A Promise that resolves to an array of DecodedMessage objects. */ @@ -149,8 +140,8 @@ export class Dm this.client, this.id, opts?.limit, - opts?.before, - opts?.after, + opts?.beforeNs, + opts?.afterNs, opts?.direction ) } @@ -176,10 +167,9 @@ export class Dm async streamMessages( callback: (message: DecodedMessage) => Promise ): Promise<() => void> { - await XMTP.subscribeToConversationMessages(this.client.inboxId, this.id) - const hasSeen = {} + await XMTP.subscribeToMessages(this.client.inboxId, this.id) const messageSubscription = XMTP.emitter.addListener( - EventTypes.ConversationV3Message, + EventTypes.ConversationMessage, async ({ inboxId, message, @@ -196,11 +186,6 @@ export class Dm if (conversationId !== this.id) { return } - if (hasSeen[message.id]) { - return - } - - hasSeen[message.id] = true message.client = this.client await callback(DecodedMessage.fromObject(message, this.client)) @@ -208,10 +193,7 @@ export class Dm ) return async () => { messageSubscription.remove() - await XMTP.unsubscribeFromConversationMessages( - this.client.inboxId, - this.id - ) + await XMTP.unsubscribeFromMessages(this.client.inboxId, this.id) } } @@ -219,11 +201,7 @@ export class Dm encryptedMessage: string ): Promise> { try { - return await XMTP.processConversationMessage( - this.client, - this.id, - encryptedMessage - ) + return await XMTP.processMessage(this.client, this.id, encryptedMessage) } catch (e) { console.info('ERROR in processConversationMessage()', e) throw e @@ -231,7 +209,7 @@ export class Dm } async consentState(): Promise { - return await XMTP.conversationV3ConsentState(this.client.inboxId, this.id) + return await XMTP.conversationConsentState(this.client.inboxId, this.id) } async updateConsent(state: ConsentState): Promise { diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 32c652613..419a44beb 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -1,9 +1,6 @@ import { InboxId } from './Client' import { ConsentState } from './ConsentListEntry' -import { - ConversationBase, - ConversationVersion, -} from './Conversation' +import { ConversationBase, ConversationVersion } from './Conversation' import { DecodedMessage } from './DecodedMessage' import { Member } from './Member' import { ConversationSendPayload } from './types/ConversationCodecs' @@ -12,14 +9,14 @@ import { EventTypes } from './types/EventTypes' import { MessagesOptions } from './types/MessagesOptions' import { PermissionPolicySet } from './types/PermissionPolicySet' import * as XMTP from '../index' +import { Address, ConversationId, ConversationTopic } from '../index' export type PermissionUpdateOption = 'allow' | 'deny' | 'admin' | 'super_admin' export interface GroupParams { - id: string + id: ConversationId createdAt: number - members: string[] - topic: string + topic: ConversationTopic name: string isActive: boolean addedByInboxId: InboxId @@ -34,10 +31,10 @@ export class Group< > implements ConversationBase { client: XMTP.Client - id: string + id: ConversationId createdAt: number version = ConversationVersion.GROUP as const - topic: string + topic: ConversationTopic name: string isGroupActive: boolean addedByInboxId: InboxId @@ -132,11 +129,7 @@ export class Group< content = { text: content } } - return await XMTP.prepareConversationMessage( - this.client.inboxId, - this.id, - content - ) + return await XMTP.prepareMessage(this.client.inboxId, this.id, content) } catch (e) { console.info('ERROR in prepareGroupMessage()', e.message) throw e @@ -150,10 +143,7 @@ export class Group< */ async publishPreparedMessages() { try { - return await XMTP.publishPreparedGroupMessages( - this.client.inboxId, - this.id - ) + return await XMTP.publishPreparedMessages(this.client.inboxId, this.id) } catch (e) { console.info('ERROR in publishPreparedMessages()', e.message) throw e @@ -177,8 +167,8 @@ export class Group< this.client, this.id, opts?.limit, - opts?.before, - opts?.after, + opts?.beforeNs, + opts?.afterNs, opts?.direction ) } @@ -204,10 +194,9 @@ export class Group< async streamMessages( callback: (message: DecodedMessage) => Promise ): Promise<() => void> { - await XMTP.subscribeToGroupMessages(this.client.inboxId, this.id) - const hasSeen = {} + await XMTP.subscribeToMessages(this.client.inboxId, this.id) const messageSubscription = XMTP.emitter.addListener( - EventTypes.GroupMessage, + EventTypes.ConversationMessage, async ({ inboxId, message, @@ -224,11 +213,6 @@ export class Group< if (groupId !== this.id) { return } - if (hasSeen[message.id]) { - return - } - - hasSeen[message.id] = true message.client = this.client await callback(DecodedMessage.fromObject(message, this.client)) @@ -236,22 +220,16 @@ export class Group< ) return async () => { messageSubscription.remove() - await XMTP.unsubscribeFromGroupMessages(this.client.inboxId, this.id) + await XMTP.unsubscribeFromMessages(this.client.inboxId, this.id) } } - async streamGroupMessages( - callback: (message: DecodedMessage) => Promise - ): Promise<() => void> { - return this.streamMessages(callback) - } - /** * * @param addresses addresses to add to the group * @returns */ - async addMembers(addresses: string[]): Promise { + async addMembers(addresses: Address[]): Promise { return XMTP.addGroupMembers(this.client.inboxId, this.id, addresses) } @@ -260,7 +238,7 @@ export class Group< * @param addresses addresses to remove from the group * @returns */ - async removeMembers(addresses: string[]): Promise { + async removeMembers(addresses: Address[]): Promise { return XMTP.removeGroupMembers(this.client.inboxId, this.id, addresses) } @@ -606,11 +584,7 @@ export class Group< encryptedMessage: string ): Promise> { try { - return await XMTP.processConversationMessage( - this.client, - this.id, - encryptedMessage - ) + return await XMTP.processMessage(this.client, this.id, encryptedMessage) } catch (e) { console.info('ERROR in processGroupMessage()', e) throw e @@ -618,7 +592,7 @@ export class Group< } async consentState(): Promise { - return await XMTP.conversationV3ConsentState(this.client.inboxId, this.id) + return await XMTP.conversationConsentState(this.client.inboxId, this.id) } async updateConsent(state: ConsentState): Promise { @@ -629,20 +603,6 @@ export class Group< ) } - /** - * @returns {Promise} a boolean indicating whether the group is allowed by the user. - */ - async isAllowed(): Promise { - return await XMTP.isGroupAllowed(this.client.inboxId, this.id) - } - - /** - * @returns {Promise} a boolean indicating whether the group is denied by the user. - */ - async isDenied(): Promise { - return await XMTP.isGroupDenied(this.client.inboxId, this.id) - } - /** * * @returns {Promise} A Promise that resolves to an array of Member objects. diff --git a/src/lib/InboxState.ts b/src/lib/InboxState.ts index cef9593f4..bff41bbb1 100644 --- a/src/lib/InboxState.ts +++ b/src/lib/InboxState.ts @@ -1,5 +1,5 @@ -import { Address } from '../utils/address' import { InboxId } from './Client' +import { Address } from '../utils/address' export class InboxState { inboxId: InboxId diff --git a/src/lib/Member.ts b/src/lib/Member.ts index fcd9d5e25..7570eb4de 100644 --- a/src/lib/Member.ts +++ b/src/lib/Member.ts @@ -1,6 +1,6 @@ -import { Address } from '../utils/address' import { InboxId } from './Client' import { ConsentState } from './ConsentListEntry' +import { Address } from '../utils/address' export type PermissionLevel = 'member' | 'admin' | 'super_admin' diff --git a/src/lib/PrivatePreferences.ts b/src/lib/PrivatePreferences.ts new file mode 100644 index 000000000..e0b4f2aa9 --- /dev/null +++ b/src/lib/PrivatePreferences.ts @@ -0,0 +1,42 @@ +import { Client, InboxId } from './Client' +import { ConsentListEntry, ConsentState } from './ConsentListEntry' +import * as XMTPModule from '../index' +import { ConversationId } from '../index' +import { Address, getAddress } from '../utils/address' + +export default class PrivatePreferences { + client: Client + + constructor(client: Client) { + this.client = client + } + + async consentConversationIdState( + conversationId: ConversationId + ): Promise { + return await XMTPModule.consentConversationIdState( + this.client.inboxId, + conversationId + ) + } + + async consentInboxIdState(inboxId: InboxId): Promise { + return await XMTPModule.consentInboxIdState(this.client.inboxId, inboxId) + } + + async consentAddressState(address: Address): Promise { + return await XMTPModule.consentAddressState( + this.client.inboxId, + getAddress(address) + ) + } + + async setConsentState(consentEntry: ConsentListEntry): Promise { + return await XMTPModule.setConsentState( + this.client.inboxId, + consentEntry.value, + consentEntry.entryType, + consentEntry.permissionType + ) + } +} diff --git a/src/lib/types/MessagesOptions.ts b/src/lib/types/MessagesOptions.ts index a8688ebc3..fa3b38f27 100644 --- a/src/lib/types/MessagesOptions.ts +++ b/src/lib/types/MessagesOptions.ts @@ -1,7 +1,7 @@ export type MessagesOptions = { limit?: number | undefined - before?: number | Date | undefined - after?: number | Date | undefined + beforeNs?: number | undefined + afterNs?: number | undefined direction?: MessageOrder | undefined } diff --git a/src/utils/address.ts b/src/utils/address.ts index ec3045e82..ca15683e9 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -4,7 +4,7 @@ import { TextEncoder } from 'text-encoding' const addressRegex = /^0x[a-fA-F0-9]{40}$/ const encoder = new TextEncoder() -export type Address = string & { readonly brand: unique symbol } +export type Address = string export function stringToBytes(value: string): Uint8Array { const bytes = encoder.encode(value)