Skip to content

v11.0.0

Compare
Choose a tag to compare
@github-actions github-actions released this 15 Sep 18:59
· 449 commits to main since this release
af369fb

11.0.0 (2023-09-15)

New features

Improved server-side performance

If you’re a Node.js developer, this is the release you’ve been waiting for.

We’ve added key extensibility points to the SDK enabling you to replace components designed for the browser with higher-performance options specifically designed to work in Node.js.

Pluggable GRPC API client

By default, xmtp-js uses the HttpApiClient to connect to the XMTP network over HTTP using Fetch. Using this default API client can be expensive and isn’t well-designed for long-lived streaming sessions. Because the XMTP web API doesn’t support bidirectional streaming, any time you need to update the content topics in a stream (for example, every time a new conversation is started in streamAllMessages) the default API client needs to disconnect and reconnect the stream. This behaviour can cause missed messages, especially in cases of high-traffic bots with many ongoing conversations.

With xmtp-js v11, you can now use a new package @xmtp/grpc-api-client to replace the default API client. This new client offers faster encoding/decoding by using protobuf and GRPC directly. The client also supports bidirectional streams over GRPC.

To take advantage of this pluggable GRPC API client, just instantiate your client with an apiClientFactory specified.

import { Client } from '@xmtp/xmtp-js'
import { GrpcApiClient } from '@xmtp/grpc-api-client'

const client = await Client.create(someWallet, { 
	apiClientFactory: GrpcApiClient.fromOptions
})

This will not work in the browser.

The GRPC API Client is currently in alpha status and is not yet ready to be used in production applications. Follow this issue for more details.

Pluggable persistence

Conversation caching in xmtp-js was initially designed for the browser, reliant on the LocalStorage API. In Node.js, caching wasn’t persistent and was started fresh every time you created a client.

You can now use a pluggable engine for the base persistence layer, enabling you to drop in something more suitable for your server-side environment.

import { Client } from '@xmtp/xmtp-js'

const client = await Client.create(someWallet, {
	basePersistence: new MyCustomPersistence()
})

We’ve created a few reference implementations of our Persistence interface that you can drop in. For example, take a look at @xmtp/redis-persistence and @xmtp/fs-persistence in the experimental new Bot Kit Pro repo.

More robust conversation caching

Conversation caching only worked for XMTP V2 conversations. While these are the vast majority of conversations on the network, some long-time users have many V1 conversations that need to be refreshed every time the client is instantiated. The conversation caching system has been overhauled to be more reliable and to support both V1 and V2 conversations.

MetaMask Snaps support

With the public release of MetaMask Snaps, developers can finally start experimenting with supporting the “Sign in with XMTP” Snap.

This Snap offers a more secure and simpler sign-in experience. If a user has signed in to any app built with XMTP using this Snap, you won’t need to prompt for signatures when they sign in to your app.

This removes the need for Client.getKeys() for any user who has the “Sign in with XMTP” Snap enabled. You can persist their sign-in session for 30 days in the Snap without ever having to touch the user’s XMTP keys. Even after 30 days, no new signatures are required.

To enable the Snap in your browser-based app, just set the useSnaps flag to true as part of client creation.

Typed message content

A frequent source of bugs in apps built with XMTP is mishandling custom content types. The SDKs were definitely part of the problem since they return message.content as an any type. The same is true of conversation.send(...), where the message argument is an any type.

v11 adds support for inferring message content types based on the list of codecs provided as part of client instantiation. None of the errors underlined in red below would have surfaced without this support in place.

xmtp js code sample with strong typing

If no custom codecs are provided, the possible types for message content are string | undefined.

Upgrade from v10 to v11

Take advantage of typed message content

Typed message content is opt-in, unless it can automatically be inferred.

For simple examples like the one above—cases where you are creating a Client and using it directly—everything should work out of the box. Things get a little trickier as you pass types around in your app. Consider the following React component:

import React from 'react'
import { DecodedMessage, Client } from '@xmtp/xmtp-js'

type Props = {
  message: DecodedMessage
}

export const MyComponent = ({ message }: Props) => {
  // message.content will be an any type here
  return <span>{message.content}</span>
}

Because the Props are explicitly typed as DecodedMessage, which defaults to DecodedMessage<any>, you won’t see the benefits of a typed client here. You can update the Props to fix this:

type Props = {
	message: DecodedMessage<string>
}

The same applies to instances of Client and Conversation as well.

The default generic type for Client, Conversation, and DecodedMessage will become unknown in a future major version release, so this is a good time to start making your content types explicit.

Breaking changes

  • We have changed how fallback text is generated for custom content types. Previously, it was set by the sender as a SendOption. For example: conversation.send(myFancyType, { contentType: MyFancyContentTypeId, fallbackText: 'some text' })

Fallbacks are now handled by the codecs directly. The fallbackText field has been removed from SendOptions.

  • If you’re building a low-level app that accesses client.apiClient directly for streaming (this is pretty rare), return type of the Subscribe method has changed. Previously it returned an unsubscribe function. Now it returns a SubscriptionManager of this type:

    export type UnsubscribeFn = () => Promise<void>
    
    export type UpdateContentTopics = (topics: string[]) => Promise<void>
    
    export type SubscriptionManager = {
      unsubscribe: UnsubscribeFn
      updateContentTopics?: UpdateContentTopics
    }
  • After you upgrade your app to use xmtp-js v11, the first time you call conversations.list() for a user, their conversation cache will be recreated from scratch. This may be slow for users with a large number of conversations. Subsequent calls will be cached and snappy.

  • Client.getKeys may throw an error if you’ve enabled the useSnaps option as part of client creation, and the user has a Snaps-compatible version of MetaMask. To help avoid these errors, the Client.isSnapsReady helper method has been added to detect whether the user is on a Snaps-compatible version of MetaMask.

  • The value of the message field on each Envelope returned from ApiClient is now always Uint8Array. Previously it could also be a string.

Upgrade tasks

  • If you are using one of the custom content types from @xmtp/xmtp-js-content-types, you must upgrade to the latest version. Upgrading is mandatory to ensure that content fallback text works as designed. After upgrading, your app will use the content fallbacks handled directly by the codecs.
  • If you are the author of a custom content type codec, please add a fallback method to your codec to return valid fallback text. If the specifics of your content type don’t lend themselves well to fallback text, and you would rather hide the message in clients that don’t support your codec, you can simply return undefined. For examples, see this pull request.
  • If you are sending any custom content types, please remove the fallbackText from your calls to conversation.send(...). It’s no longer needed.