Skip to content

Commit 4e4bdbc

Browse files
authored
Merge pull request #208 from xmtp/beta
V7 Release
2 parents ce7a8d6 + 907acb7 commit 4e4bdbc

28 files changed

+1904
-1191
lines changed

.github/workflows/release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ on:
33
push:
44
branches:
55
- main
6+
- beta
67
jobs:
78
release:
89
name: Release

README.md

+49-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ webpack: (config, { isServer }) => {
8080

8181
## Usage
8282

83-
The API revolves around a network Client that allows retrieving and sending messages to other network participants. A Client must be connected to a wallet on startup. If this is the very first time the Client is created, the client will generate a key bundle that is used to encrypt and authenticate messages. The key bundle persists encrypted in the network using a wallet signature, or optionally in local storage. The public side of the key bundle is also regularly advertised on the network to allow parties to establish shared encryption keys. All this happens transparently, without requiring any additional code.
83+
The API revolves around a network Client that allows retrieving and sending messages to other network participants. A Client must be connected to a wallet on startup. If this is the very first time the Client is created, the client will generate a key bundle that is used to encrypt and authenticate messages. The key bundle persists encrypted in the network using a wallet signature. The public side of the key bundle is also regularly advertised on the network to allow parties to establish shared encryption keys. All this happens transparently, without requiring any additional code.
8484

8585
```ts
8686
import { Client } from '@xmtp/xmtp-js'
@@ -128,7 +128,7 @@ The client's network connection and key storage method can be configured with th
128128
| env | `dev` | Connect to the specified XMTP network environment. Valid values also include `production` and `local`. For important details about working with these environments, see [XMTP `production` and `dev` network environments](#xmtp-production-and-dev-network-environments). |
129129
| apiUrl | `undefined` | Manually specify an API URL to use. If specified, value of `env` will be ignored. |
130130
| |
131-
| keyStoreType | `networkTopicStoreV1` | Persist the wallet's key bundle to the network, or optionally to `localStorage`. |
131+
| keyStoreType | `networkTopicStoreV1` | Persist the wallet's key bundle to the network, or use `static` to provide private keys manually. |
132132
| codecs | `[TextCodec]` | Add codecs to support additional content types. |
133133
| maxContentSize | `100M` | Maximum message content size in bytes. |
134134

@@ -267,6 +267,53 @@ const isOnProdNetwork = await Client.canMessage(
267267
)
268268
```
269269

270+
#### Handling multiple conversations with the same blockchain address
271+
272+
With XMTP, you can have multiple ongoing conversations with the same blockchain address. For example, you might want to have a conversation scoped to your particular application, or even a conversation scoped to a particular item in your application.
273+
274+
To accomplish this, just set the `conversationId` when you are creating a conversation. We recommend conversation IDs start with a domain, to help avoid unwanted collisions between your application and other apps on the XMTP network.
275+
276+
```ts
277+
// Start a scoped conversation with ID mydomain.xyz/foo
278+
const conversation1 = await xmtp.conversations.newConversation(
279+
'0x3F11b27F323b62B159D2642964fa27C46C841897',
280+
{
281+
conversationId: 'mydomain.xyz/foo',
282+
}
283+
)
284+
285+
// Start a scoped conversation with ID mydomain.xyz/bar. And add some metadata
286+
const conversation2 = await xmtp.conversations.newConversation(
287+
'0x3F11b27F323b62B159D2642964fa27C46C841897',
288+
{
289+
conversationId: 'mydomain.xyz/bar',
290+
metadata: {
291+
title: 'Bar conversation',
292+
},
293+
}
294+
)
295+
296+
// Get all the conversations
297+
const conversations = await xmtp.conversations.list()
298+
// Filter for the ones from your application
299+
const myAppConversations = conversations.filter(
300+
(convo) =>
301+
convo.context?.conversationId &&
302+
convo.context.conversationId.startsWith('mydomain.xyz/')
303+
)
304+
305+
for (const conversation of myAppConversations) {
306+
const conversationId = conversation.context?.conversationId
307+
if (conversationId === 'mydomain.xyz/foo') {
308+
await conversation.send('foo')
309+
}
310+
if (conversationId === 'mydomain.xyz/bar') {
311+
await conversation.send('bar')
312+
console.log(conversation.context?.metadata.title)
313+
}
314+
}
315+
```
316+
270317
#### Different types of content
271318

272319
All the send functions support `SendOptions` as an optional parameter. The `contentType` option allows specifying different types of content than the default simple string, which is identified with content type identifier `ContentTypeText`. Support for other types of content can be added by registering additional `ContentCodecs` with the `Client`. Every codec is associated with a content type identifier, `ContentTypeId`, which is used to signal to the Client which codec should be used to process the content that is being sent or received. See [XIP-5](https://github.com/xmtp/XIPs/blob/main/XIPs/xip-5-message-content-types.md) for more details on codecs and content types.

package-lock.json

+15-66
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+9-7
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
"clean": "node tools/cleanup && npm run clean:proto",
1818
"clean:proto": "rm -rf src/proto/*.ts",
1919
"package": "npm pack",
20-
"prepare": "npm run build",
20+
"prepublish": "npm run build",
2121
"test": "npm run test:node",
22-
"test:node": "jest --no-cache --runInBand --env='node' --forceExit --testTimeout=30000",
23-
"test:jsdom": "jest --no-cache --runInBand --env='./jest.jsdom.env.js' --forceExit --testTimeout=30000",
22+
"test:node": "jest --no-cache --env='node' --forceExit --testTimeout=30000",
23+
"test:jsdom": "jest --no-cache --env='./jest.jsdom.env.js' --forceExit --testTimeout=30000",
2424
"test:cov": "jest --coverage --no-cache --runInBand",
2525
"lint": "prettier --check . && eslint .",
2626
"autolint": "prettier --write . && eslint --fix .",
@@ -55,16 +55,18 @@
5555
"release": {
5656
"branches": [
5757
"main",
58-
"next"
58+
{
59+
"name": "beta",
60+
"prerelease": true
61+
}
5962
]
6063
},
6164
"dependencies": {
6265
"@noble/secp256k1": "^1.5.2",
6366
"@stardazed/streams-polyfill": "^2.4.0",
64-
"@xmtp/proto": "^3.2.0",
67+
"@xmtp/proto": "^3.4.0",
6568
"ethers": "^5.5.3",
66-
"long": "^5.2.0",
67-
"node-localstorage": "^2.2.1"
69+
"long": "^5.2.0"
6870
},
6971
"devDependencies": {
7072
"@commitlint/cli": "^16.1.0",

src/ApiClient.ts

+19-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { messageApi } from '@xmtp/proto'
22
import { NotifyStreamEntityArrival } from '@xmtp/proto/ts/dist/types/fetch.pb'
3-
import { dateToNs, retry, sleep } from './utils'
3+
import { retry, sleep, toNanoString } from './utils'
44
import AuthCache from './authn/AuthCache'
55
import { Authenticator } from './authn'
66
import { version } from '../package.json'
@@ -10,6 +10,7 @@ const RETRY_SLEEP_TIME = 100
1010
const ERR_CODE_UNAUTHENTICATED = 16
1111

1212
const clientVersionHeaderKey = 'X-Client-Version'
13+
const appVersionHeaderKey = 'X-App-Version'
1314

1415
export type GrpcError = Error & { code?: number }
1516

@@ -40,16 +41,13 @@ export type SubscribeParams = {
4041

4142
export type ApiClientOptions = {
4243
maxRetries?: number
44+
appVersion?: string
4345
}
4446

4547
export type SubscribeCallback = NotifyStreamEntityArrival<messageApi.Envelope>
4648

4749
export type UnsubscribeFn = () => Promise<void>
4850

49-
const toNanoString = (d: Date | undefined): undefined | string => {
50-
return d && dateToNs(d).toString()
51-
}
52-
5351
const isAbortError = (err?: Error): boolean => {
5452
if (!err) {
5553
return false
@@ -79,11 +77,13 @@ export default class ApiClient {
7977
pathPrefix: string
8078
maxRetries: number
8179
private authCache?: AuthCache
80+
appVersion: string | undefined
8281
version: string
8382

8483
constructor(pathPrefix: string, opts?: ApiClientOptions) {
8584
this.pathPrefix = pathPrefix
8685
this.maxRetries = opts?.maxRetries || 5
86+
this.appVersion = opts?.appVersion
8787
this.version = 'xmtp-js/' + version
8888
}
8989

@@ -98,9 +98,7 @@ export default class ApiClient {
9898
{
9999
pathPrefix: this.pathPrefix,
100100
mode: 'cors',
101-
headers: new Headers({
102-
[clientVersionHeaderKey]: this.version,
103-
}),
101+
headers: this.headers(),
104102
},
105103
],
106104
this.maxRetries,
@@ -114,6 +112,8 @@ export default class ApiClient {
114112
attemptNumber = 0
115113
): ReturnType<typeof MessageApi.Publish> {
116114
const authToken = await this.getToken()
115+
const headers = this.headers()
116+
headers.set('Authorization', `Bearer ${authToken}`)
117117
try {
118118
return await retry(
119119
MessageApi.Publish,
@@ -122,10 +122,7 @@ export default class ApiClient {
122122
{
123123
pathPrefix: this.pathPrefix,
124124
mode: 'cors',
125-
headers: new Headers({
126-
Authorization: `Bearer ${authToken}`,
127-
[clientVersionHeaderKey]: this.version,
128-
}),
125+
headers,
129126
},
130127
],
131128
this.maxRetries,
@@ -159,9 +156,7 @@ export default class ApiClient {
159156
pathPrefix: this.pathPrefix,
160157
signal: abortController.signal,
161158
mode: 'cors',
162-
headers: new Headers({
163-
[clientVersionHeaderKey]: this.version,
164-
}),
159+
headers: this.headers(),
165160
}).catch(async (err: GrpcError) => {
166161
if (isAbortError(err)) {
167162
return
@@ -315,4 +310,13 @@ export default class ApiClient {
315310
): void {
316311
this.authCache = new AuthCache(authenticator, cacheExpirySeconds)
317312
}
313+
314+
headers(): Headers {
315+
const headers = new Headers()
316+
headers.set(clientVersionHeaderKey, this.version)
317+
if (this.appVersion) {
318+
headers.set(appVersionHeaderKey, this.appVersion)
319+
}
320+
return headers
321+
}
318322
}

0 commit comments

Comments
 (0)