diff --git a/example/src/TestScreen.tsx b/example/src/TestScreen.tsx index f8eded84..c5eb979c 100644 --- a/example/src/TestScreen.tsx +++ b/example/src/TestScreen.tsx @@ -3,6 +3,7 @@ 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' @@ -10,7 +11,6 @@ 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({ @@ -114,6 +114,7 @@ export enum TestCategory { restartStreans = 'restartStreams', groupPermissions = 'groupPermissions', groupPerformance = 'groupPerformance', + contentType = 'contentType', } export default function TestScreen(): JSX.Element { @@ -129,6 +130,7 @@ export default function TestScreen(): JSX.Element { ...conversationTests, ...restartStreamTests, ...groupPermissionsTests, + ...contentTypeTests, ] let activeTests, title switch (params.testSelection) { @@ -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 ( diff --git a/example/src/tests/contentTypeTests.ts b/example/src/tests/contentTypeTests.ts new file mode 100644 index 00000000..0df03aa7 --- /dev/null +++ b/example/src/tests/contentTypeTests.ts @@ -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) { + 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 +}) diff --git a/example/src/tests/test-utils.ts b/example/src/tests/test-utils.ts index d920f0aa..ce0f53f8 100644 --- a/example/src/tests/test-utils.ts +++ b/example/src/tests/test-utils.ts @@ -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 @@ -34,6 +39,7 @@ export async function createClients(numClients: number): Promise { dbEncryptionKey: keyBytes, }) client.register(new GroupUpdatedCodec()) + client.register(new RemoteAttachmentCodec()) clients.push(client) } return clients