Skip to content

Commit

Permalink
Merge pull request #500 from xmtp/rygine/resolve-conflicts
Browse files Browse the repository at this point in the history
`main` => `beta`
  • Loading branch information
rygine authored Dec 5, 2023
2 parents ae74d4b + 4bb7c33 commit 43c270d
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 37 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 XMTP Labs (xmtp.com)
Copyright (c) 2023 XMTP (xmtp.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
25 changes: 5 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,15 @@ Additional configuration is required in React environments due to the removal of

## Troubleshoot

If you get into issues with Buffer and polyfills check out our [fix below](https://xmtp.org/docs/developer-quickstart#troubleshooting).
### Buffer polyfill

### Create React App
If you run into issues with Buffer and polyfills, see this [solution](https://xmtp.org/docs/faq#why-my-app-is-failing-saying-buffer-is-not-found).

Use `react-scripts` prior to version `5.0.0`. For example:
### BigInt polyfill

```bash
npx create-react-app my-app --scripts-version 4.0.2
```

Or downgrade after creating your app.

### Next.js

In `next.config.js`:
This SDK uses `BigInt` in a way that's incompatible with polyfills. To ensure that a polyfill isn't added to your application bundle, update your [browserslist](https://github.com/browserslist/browserslist) configuration to exclude browsers that don't support `BigInt`.

```js
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback.fs = false
}
return config
}
```
For the list of browsers that don't support `BigInt`, see this [compatibility list](https://caniuse.com/bigint).

## Usage

Expand Down
7 changes: 1 addition & 6 deletions src/ApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,13 @@ const isAbortError = (err?: Error): boolean => {
return false
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isAuthError = (err?: GrpcError | Error): boolean => {
if (err && 'code' in err && err.code === ERR_CODE_UNAUTHENTICATED) {
return true
}
return false
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isNotAuthError = (err?: Error): boolean => !isAuthError(err)

export interface ApiClient {
Expand Down Expand Up @@ -290,10 +288,7 @@ export default class HttpApiClient implements ApiClient {
if (isAbortError(err) || abortController.signal.aborted) {
return
}
console.info(
'Stream connection closed. Resubscribing',
err.toString()
)
console.info('Stream connection closed. Resubscribing')

if (new Date().getTime() - startTime < 1000) {
await sleep(1000)
Expand Down
2 changes: 1 addition & 1 deletion src/Invitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class InvitationV1 implements invitation.InvitationV1 {
.replace(/=*$/g, '')
// Replace slashes with dashes so that the topic is still easily split by /
// We do not treat this as needing to be valid Base64 anywhere
.replace('/', '-')
.replace(/\//g, '-')
)
const keyMaterial = crypto.getRandomValues(new Uint8Array(32))

Expand Down
21 changes: 12 additions & 9 deletions src/conversations/Conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
buildUserIntroTopic,
buildUserInviteTopic,
dateToNs,
isValidTopic,
nsToDate,
} from '../utils'
import { PublicKeyBundle } from '../crypto'
Expand Down Expand Up @@ -77,14 +78,14 @@ export default class Conversations<ContentTypes = any> {
})

await this.client.keystore.saveV1Conversations({
conversations: Array.from(seenPeers).map(
([peerAddress, createdAt]) => ({
conversations: Array.from(seenPeers)
.map(([peerAddress, createdAt]) => ({
peerAddress,
createdNs: dateToNs(createdAt),
topic: buildDirectMessageTopic(peerAddress, this.client.address),
context: undefined,
})
),
}))
.filter((c) => isValidTopic(c.topic)),
})

return (
Expand Down Expand Up @@ -152,11 +153,13 @@ export default class Conversations<ContentTypes = any> {
shouldThrow = false
): Promise<ConversationV2<ContentTypes>[]> {
const { responses } = await this.client.keystore.saveInvites({
requests: envelopes.map((env) => ({
payload: env.message as Uint8Array,
timestampNs: Long.fromString(env.timestampNs as string),
contentTopic: env.contentTopic as string,
})),
requests: envelopes
.map((env) => ({
payload: env.message as Uint8Array,
timestampNs: Long.fromString(env.timestampNs as string),
contentTopic: env.contentTopic as string,
}))
.filter((req) => isValidTopic(req.contentTopic)),
})

const out: ConversationV2<ContentTypes>[] = []
Expand Down
15 changes: 15 additions & 0 deletions src/utils/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,18 @@ export const buildUserPrivateStoreTopic = (addrPrefixedKey: string): string => {

export const buildUserPrivatePreferencesTopic = (identifier: string) =>
buildContentTopic(`user-preferences-${identifier}`)

// validate that a topic only contains ASCII characters 33-127
export const isValidTopic = (topic: string): boolean => {
// eslint-disable-next-line no-control-regex
const regex = /^[\x21-\x7F]+$/
const index = topic.indexOf('0/')
if (index !== -1) {
const unwrappedTopic = topic.substring(
index + 2,
topic.lastIndexOf('/proto')
)
return regex.test(unwrappedTopic)
}
return false
}
48 changes: 48 additions & 0 deletions test/utils/topic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
buildContentTopic,
buildDirectMessageTopicV2,
isValidTopic,
} from '../../src/utils/topic'
import crypto from '../../src/crypto/crypto'

describe('topic utils', () => {
describe('isValidTopic', () => {
it('validates topics correctly', () => {
expect(isValidTopic(buildContentTopic('foo'))).toBe(true)
expect(isValidTopic(buildContentTopic('123'))).toBe(true)
expect(isValidTopic(buildContentTopic('bar987'))).toBe(true)
expect(isValidTopic(buildContentTopic('*&+-)'))).toBe(true)
expect(isValidTopic(buildContentTopic('%#@='))).toBe(true)
expect(isValidTopic(buildContentTopic('<;.">'))).toBe(true)
expect(isValidTopic(buildContentTopic(String.fromCharCode(33)))).toBe(
true
)
expect(isValidTopic(buildContentTopic('∫ß'))).toBe(false)
expect(isValidTopic(buildContentTopic('\xA9'))).toBe(false)
expect(isValidTopic(buildContentTopic('\u2665'))).toBe(false)
expect(isValidTopic(buildContentTopic(String.fromCharCode(1)))).toBe(
false
)
expect(isValidTopic(buildContentTopic(String.fromCharCode(23)))).toBe(
false
)
})

it('validates random topics correctly', () => {
const topics = Array.from({ length: 100 }).map(() =>
buildDirectMessageTopicV2(
Buffer.from(crypto.getRandomValues(new Uint8Array(32)))
.toString('base64')
.replace(/=*$/g, '')
// Replace slashes with dashes so that the topic is still easily split by /
// We do not treat this as needing to be valid Base64 anywhere
.replace(/\//g, '-')
)
)

topics.forEach((topic) => {
expect(isValidTopic(topic)).toBe(true)
})
})
})
})

0 comments on commit 43c270d

Please sign in to comment.