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.
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.
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.
Summary
xmtp-js
v11 releaseNew 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 theHttpApiClient
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 instreamAllMessages
) 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.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 theLocalStorage
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.
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 totrue
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 anany
type. The same is true ofconversation.send(...)
, where the message argument is anany
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.
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:Because the
Props
are explicitly typed asDecodedMessage
, which defaults toDecodedMessage<any>
, you won’t see the benefits of a typed client here. You can update theProps
to fix this:The same applies to instances of
Client
andConversation
as well.The default generic type for
Client
,Conversation
, andDecodedMessage
will becomeunknown
in a future major version release, so this is a good time to start making your content types explicit.Breaking changes
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 fromSendOption
s.If you’re building a low-level app that accesses
client.apiClient
directly for streaming (this is pretty rare), return type of theSubscribe
method has changed. Previously it returned an unsubscribe function. Now it returns aSubscriptionManager
of this type:After you upgrade your app to use
xmtp-js
v11, the first time you callconversations.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 theuseSnaps
option as part of client creation, and the user has a Snaps-compatible version of MetaMask. To help avoid these errors, theClient.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 eachEnvelope
returned fromApiClient
is now alwaysUint8Array
. Previously it could also be astring
.Upgrade tasks
@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.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 returnundefined
. For examples, see this pull request.fallbackText
from your calls toconversation.send(...)
. It’s no longer needed.