Skip to content

Commit

Permalink
Merge pull request #558 from xmtp/cv/example-app-fix-remote-attachment
Browse files Browse the repository at this point in the history
Fixes remote attachments testing in example app
  • Loading branch information
cameronvoell authored Dec 9, 2024
2 parents 92e712a + 2eb0bbd commit 4a20699
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 53 deletions.
28 changes: 24 additions & 4 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ npx pod-install
yarn run [ios or android]
```

### If testing the remote attachment content type, run (from example directory):

```bash
yarn run upload:up
```

If running on andoid also run:

```bash
adb reverse tcp:8443 tcp:8443
```

To clean up after testing you can run:

```bash
yarn run upload:down
```

### Configure ThirdWeb Client API

> Note - The connect wallet button will still work without adding a client id, you just may see some extra network errors when viewing account info in the Thirdweb button after connecting.
Expand All @@ -38,19 +56,21 @@ Running tests locally is useful when updating GitHub actions, or locally testing

1. [Install Docker](https://docs.docker.com/get-docker/)

2. Start a local XMTP server (from example directory)
2. Start a local XMTP server
```bash
docker-compose -p xmtp -f dev/local/docker-compose.yml up -d
git clone https://github.com/xmtp/libxmtp.git
cd libxmtp
dev/up
```
3. Verify the XMTP server is running
```bash
docker-compose ls
NAME STATUS CONFIG FILES
xmtp running(3) <REPO_DIRECTORY>/xmtp-react-native/example/dev/local/docker-compose.yml
libxmtp running(9) <REPO_DIRECTORY>/libxmtp/dev/docker/docker-compose.yml
```
4. You can now run unit tests on your local emulators
5. You can stop the XMTP server with the following command:
```bash
docker-compose -p xmtp -f dev/local/docker-compose.yml down
dev/down
```
35 changes: 1 addition & 34 deletions example/dev/local/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,5 @@
version: "3.8"
services:
wakunode:
image: xmtp/node-go:latest
platform: linux/amd64
environment:
- GOWAKU-NODEKEY=8a30dcb604b0b53627a5adc054dbf434b446628d4bd1eccc681d223f0550ce67
command:
- --store.enable
- --store.db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
- --store.reader-db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
- --wait-for-db=30s
- --api.authn.enable
ports:
- 9001:9001
- 5555:5555 # http message API
- 5556:5556 # grpc message API
depends_on:
- db
healthcheck:
test: [ "CMD", "lsof", "-i", ":5556" ]
interval: 3s
timeout: 10s
retries: 5
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: xmtp
js:
restart: always
platform: linux/amd64
depends_on:
wakunode:
condition: service_healthy
build: ./test

upload-service:
build: ./upload-service
Expand All @@ -42,7 +9,7 @@ services:
caddy:
image: caddy:latest
ports:
- "443:443"
- "8443:8443"
volumes:
- ./upload-service/Caddyfile:/etc/caddy/Caddyfile
- ./upload-service/data/data:/data
Expand Down
2 changes: 1 addition & 1 deletion example/dev/local/upload-service/Caddyfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
localhost {
localhost:8443 {
tls internal
reverse_proxy upload-service:4567
}
4 changes: 3 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"android": "expo run:android",
"ios": "expo run:ios",
"ios:clean": "expo run:ios --no-build-cache",
"web": "expo start --web"
"web": "expo start --web",
"upload:up": "docker-compose -p xmtp -f dev/local/docker-compose.yml up -d",
"upload:down": "docker-compose -p xmtp -f dev/local/docker-compose.yml down"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.21.0",
Expand Down
1 change: 0 additions & 1 deletion example/src/LaunchScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { ConnectWallet, useSigner } from '@thirdweb-dev/react-native'
import React, { useCallback, useEffect, useState } from 'react'
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'
import EncryptedStorage from 'react-native-encrypted-storage'
import ModalSelector from 'react-native-modal-selector'
import * as XMTP from 'xmtp-react-native-sdk'
import { useXmtp } from 'xmtp-react-native-sdk'
Expand Down
8 changes: 7 additions & 1 deletion example/src/TestScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import React, { useEffect, useState } from 'react'
import { View, Text, Button, ScrollView } from 'react-native'

import { clientTests } from './tests/clientTests'
import { contentTypeTests } from './tests/contentTypeTests'
import { conversationTests } from './tests/conversationTests'
import { dmTests } from './tests/dmTests'
import { groupPerformanceTests } from './tests/groupPerformanceTests'
import { groupPermissionsTests } from './tests/groupPermissionsTests'
import { groupTests } from './tests/groupTests'
import { restartStreamTests } from './tests/restartStreamsTests'
import { Test } from './tests/test-utils'

type Result = 'waiting' | 'running' | 'success' | 'failure' | 'error'

function TestView({
Expand Down Expand Up @@ -114,6 +114,7 @@ export enum TestCategory {
restartStreans = 'restartStreams',
groupPermissions = 'groupPermissions',
groupPerformance = 'groupPerformance',
contentType = 'contentType',
}

export default function TestScreen(): JSX.Element {
Expand All @@ -129,6 +130,7 @@ export default function TestScreen(): JSX.Element {
...conversationTests,
...restartStreamTests,
...groupPermissionsTests,
...contentTypeTests,
]
let activeTests, title
switch (params.testSelection) {
Expand Down Expand Up @@ -164,6 +166,10 @@ export default function TestScreen(): JSX.Element {
activeTests = groupPerformanceTests
title = 'Group Performance Unit Tests'
break
case TestCategory.contentType:
activeTests = contentTypeTests
title = 'Content Type Unit Tests'
break
}

return (
Expand Down
31 changes: 21 additions & 10 deletions example/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ReactNativeBlobUtil from 'react-native-blob-util'

const useLocalServer = !process.env.REACT_APP_USE_LOCAL_SERVER
const storageUrl = useLocalServer
? 'https://localhost'
? 'https://localhost:8443'
: process.env.REACT_APP_STORAGE_URL
const headers = {
'Content-Type': 'application/octet-stream',
Expand All @@ -19,15 +19,26 @@ export async function uploadFile(
): Promise<string> {
const url = `${storageUrl}/${fileId}`
console.log('uploading to', url)
await ReactNativeBlobUtil.config({
fileCache: true,
trusty: useLocalServer,
}).fetch(
'POST',
url,
headers,
ReactNativeBlobUtil.wrap(localFileUri.slice('file://'.length))
)

try {
await ReactNativeBlobUtil.config({
fileCache: true,
trusty: useLocalServer,
}).fetch(
'POST',
url,
headers,
ReactNativeBlobUtil.wrap(localFileUri.slice('file://'.length))
)
} catch (error) {
console.error(
'Error during file upload:',
error,
'Did you run the `yarn run upload:up` command from the xmtp-react-native/example directory?',
'Did you run `adb reverse tcp:8443 tcp:8443` if testing on Android?'
)
throw error
}

return url
}
Expand Down
96 changes: 96 additions & 0 deletions example/src/tests/contentTypeTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import ReactNativeBlobUtil from 'react-native-blob-util'

import { Test, createClients, delayToPropogate } from './test-utils'
import { RemoteAttachmentContent } from '../../../src/index'
const { fs } = ReactNativeBlobUtil

export const contentTypeTests: Test[] = []
let counter = 1
function test(name: string, perform: () => Promise<boolean>) {
contentTypeTests.push({
name: String(counter++) + '. ' + name,
run: perform,
})
}

test('remote attachments should work', async () => {
const [alix, bo] = await createClients(2)
const convo = await alix.conversations.newConversation(bo.address)

// Alice is sending Bob a file from her phone.
const filename = `${Date.now()}.txt`
const file = `${fs.dirs.CacheDir}/${filename}`
await fs.writeFile(file, 'hello world', 'utf8')
const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment({
fileUri: `file://${file}`,
mimeType: 'text/plain',
})

const encryptedFile = encryptedLocalFileUri.slice('file://'.length)
const originalContent = await fs.readFile(file, 'base64')
const encryptedContent = await fs.readFile(encryptedFile, 'base64')
if (encryptedContent === originalContent) {
throw new Error('encrypted file should not match original')
}

// This is where the app will upload the encrypted file to a remote server and generate a URL.
// let url = await uploadFile(encryptedLocalFileUri);
const url = 'https://example.com/123'

// Together with the metadata, we send the URL as a remoteAttachment message to the conversation.
await convo.send({
remoteAttachment: {
...metadata,
scheme: 'https://',
url,
},
})
await delayToPropogate()

// Now we should see the remote attachment message.
const messages = await convo.messages()
if (messages.length !== 1) {
throw new Error('Expected 1 message')
}
const message = messages[0]

if (message.contentTypeId !== 'xmtp.org/remoteStaticAttachment:1.0') {
throw new Error('Expected correctly formatted typeId')
}
if (!message.content()) {
throw new Error('Expected remoteAttachment')
}
if (
(message.content() as RemoteAttachmentContent).url !==
'https://example.com/123'
) {
throw new Error('Expected url to match')
}

// This is where the app prompts the user to download the encrypted file from `url`.
// TODO: let downloadedFile = await downloadFile(url);
// But to simplify this test, we're just going to copy
// the previously encrypted file and pretend that we just downloaded it.
const downloadedFileUri = `file://${fs.dirs.CacheDir}/${Date.now()}.bin`
await fs.cp(
new URL(encryptedLocalFileUri).pathname,
new URL(downloadedFileUri).pathname
)

// Now we can decrypt the downloaded file using the message metadata.
const attached = await alix.decryptAttachment({
encryptedLocalFileUri: downloadedFileUri,
metadata: message.content() as RemoteAttachmentContent,
})
if (attached.mimeType !== 'text/plain') {
throw new Error('Expected mimeType to match')
}
if (attached.filename !== filename) {
throw new Error(`Expected ${attached.filename} to equal ${filename}`)
}
const text = await fs.readFile(new URL(attached.fileUri).pathname, 'utf8')
if (text !== 'hello world') {
throw new Error('Expected text to match')
}
return true
})
8 changes: 7 additions & 1 deletion example/src/tests/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Platform } from 'expo-modules-core'
import { Client, GroupUpdatedCodec, Group } from 'xmtp-react-native-sdk'
import {
Client,
GroupUpdatedCodec,
Group,
RemoteAttachmentCodec,
} from 'xmtp-react-native-sdk'

export type Test = {
name: string
Expand Down Expand Up @@ -34,6 +39,7 @@ export async function createClients(numClients: number): Promise<Client[]> {
dbEncryptionKey: keyBytes,
})
client.register(new GroupUpdatedCodec())
client.register(new RemoteAttachmentCodec())
clients.push(client)
}
return clients
Expand Down

0 comments on commit 4a20699

Please sign in to comment.