Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/test-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ jobs:
node-version-file: ".nvmrc"
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install and test npm dependencies
run: |
yarn install --frozen-lockfile
yarn test

- name: Install Maestro
run: |
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ jobs:
node-version-file: ".nvmrc"
cache: yarn

- name: Install dependencies
- name: Install and test npm dependencies
run: |
yarn install --frozen-lockfile
cd ./example
yarn install --frozen-lockfile
yarn test

- name: Install Maestro
run: |
Expand Down
34 changes: 19 additions & 15 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This basic messaging app has an intentionally unopinionated UI to help make it easier for you to build with.

## Run the example app

Follow the [React Native guide](https://reactnative.dev/docs/environment-setup) to set up a CLI environment.

### To use the example app, run:
Expand All @@ -11,7 +12,6 @@ Follow the [React Native guide](https://reactnative.dev/docs/environment-setup)
yarn
cd example
yarn
npx pod-install
yarn run [ios or android]
```

Expand Down Expand Up @@ -43,34 +43,38 @@ First create a free account and download your client id from https://thirdweb.co
cd example
cp EXAMPLE.env .env
```

Finally, insert your Thirdweb client id in specified location of `example/.env` file:

```
THIRD_WEB_CLIENT_ID=INSERT_CLIENT_ID_HERE
```

If your app doesn't appear to be picking up changes in the .env file, you can try editing the TypeScript file you're reading the env variable from (`App.tsx`) or building the app with the `--no-build-cache` flag added.


## Run example app unit tests on local emulators

Running tests locally is useful when updating GitHub actions, or locally testing between changes.

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

2. Start a local XMTP server
```bash
git clone https://github.com/xmtp/libxmtp.git
cd libxmtp
dev/up
```
```bash
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
libxmtp running(9) <REPO_DIRECTORY>/libxmtp/dev/docker/docker-compose.yml
```
```bash
docker-compose ls

NAME STATUS CONFIG FILES
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
dev/down
```
```bash
dev/down
```
7 changes: 7 additions & 0 deletions example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"**/*"
],
"plugins": [
[
"expo-sqlite",
{
"enableFTS": true,
"useSQLCipher": true
}
],
[
"expo-image-picker",
{
Expand Down
15 changes: 7 additions & 8 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,14 @@
"react-native": "0.76.9",
"react-native-blob-util": "^0.19.0",
"react-native-config": "^1.5.1",
"react-native-crypto": "^2.2.0",
"react-native-encrypted-storage": "^4.0.3",
"react-native-fs": "^2.20.0",
"expo-secure-store": "~14.0.1",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "^2.8.0",
"react-native-quick-base64": "^2.0.8",
"react-native-quick-crypto": "^0.7.12",
"react-native-randombytes": "^3.6.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
"react-native-sqlite-storage": "^6.0.1",
"expo-sqlite": "~15.1.4",
"react-native-svg": "15.8.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-webview": "13.12.5",
Expand Down Expand Up @@ -79,9 +76,11 @@
"expo": {
"autolinking": {
"nativeModulesDir": ".."
},
"doctor": {
"reactNativeDirectoryCheck": {
"listUnknownPackages": false
}
}
},
"engines": {
"node": ">=20"
}
}
57 changes: 36 additions & 21 deletions example/src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react'

Check warning on line 1 in example/src/hooks.tsx

View workflow job for this annotation

GitHub Actions / lint

`react` import should occur after import of `expo-file-system`
import EncryptedStorage from 'react-native-encrypted-storage'
import RNFS from 'react-native-fs'
import crypto from 'react-native-quick-crypto'
import * as ExpoCrypto from 'expo-crypto'
import * as SecureStore from 'expo-secure-store'

Check warning on line 3 in example/src/hooks.tsx

View workflow job for this annotation

GitHub Actions / lint

`expo-secure-store` import should occur after import of `expo-file-system`
import * as FileSystem from 'expo-file-system'
import { useMutation, useQuery, UseQueryResult } from 'react-query'
import {
Conversation,
Expand Down Expand Up @@ -762,16 +762,16 @@
} {
const { data: address, refetch } = useQuery<string | null>(
['xmtp', 'address'],
() => EncryptedStorage.getItem('xmtp.address')
() => SecureStore.getItem('xmtp.address')
)
return {
address,
save: async (address: string) => {
await EncryptedStorage.setItem('xmtp.address', address)
await SecureStore.setItemAsync('xmtp.address', address)
await refetch()
},
clear: async () => {
await EncryptedStorage.removeItem('xmtp.address')
await SecureStore.deleteItemAsync('xmtp.address')
await refetch()
},
}
Expand All @@ -784,11 +784,11 @@
try {
const key = `xmtp-${network}`

const result = await EncryptedStorage.getItem(key)
if ((result && clear === true) || !result) {
const result = SecureStore.getItem(key)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SecureStore calls in getDbEncryptionKey are never awaited: both the retrieval and storage methods return promises, so the function returns before persistence completes, swallows storage errors, mis-evaluates the stored-key condition (a Promise is always truthy) and even passes a Promise into hexStringToUint8Array, causing runtime errors. Consider using await SecureStore.getItemAsync(...) and await SecureStore.setItemAsync(...) so persistence finishes before returning, errors propagate, and you operate on the resolved string.

-    const result = SecureStore.getItem(key)
+    const result = await SecureStore.getItem(key)

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

if (!result || clear === true) {
if (result) {
console.log('Removing existing dbEncryptionKey', key)
await EncryptedStorage.removeItem(key)
await SecureStore.deleteItemAsync(key)
}

// Generate random bytes for the encryption key
Expand All @@ -797,13 +797,13 @@

// Convert to string for storage
const randomBytesString = uint8ArrayToHexString(randomBytes)
await EncryptedStorage.setItem(key, randomBytesString)
await SecureStore.setItemAsync(key, randomBytesString)

return randomBytes
} else {
// Convert stored string back to Uint8Array
return hexStringToUint8Array(result)
}

// Convert stored string back to Uint8Array
return hexStringToUint8Array(result)
} catch (error) {
console.error('Error in getDbEncryptionKey:', error)
// Re-throw or handle as needed
Expand Down Expand Up @@ -831,9 +831,15 @@
return new Uint8Array(byteArray)
}

function ensureFileUri(path: string): string {
return path.startsWith('file://') ? path : `file://${path}`
}

async function fileExists(path: string): Promise<boolean> {
try {
return await RNFS.exists(path)
const uri = ensureFileUri(path)
const info = await FileSystem.getInfoAsync(uri)
return info.exists
} catch (error) {
console.error('Error checking file existence:', error)
return false
Expand All @@ -842,8 +848,12 @@

async function getFileSize(path: string): Promise<number> {
try {
const stats = await RNFS.stat(path)
return stats.size
const uri = ensureFileUri(path)
const info = await FileSystem.getInfoAsync(uri, { size: true })
if (info.exists) {
return info.size
}
return 0
} catch (error) {
console.error('Error getting file size:', error)
return 0
Expand All @@ -853,13 +863,18 @@
async function calculateFileDigest(path: string): Promise<string> {
try {
// Read the file content
const fileContent = await RNFS.readFile(path, 'base64')
const uri = ensureFileUri(path)
const fileContent = await FileSystem.readAsStringAsync(uri, {
encoding: FileSystem.EncodingType.Base64,
})
const buffer = Buffer.from(fileContent, 'base64')

// Create SHA-256 hash using react-native-quick-crypto
const hash = crypto.createHash('sha256')
hash.update(buffer.buffer)
return hash.digest('hex')
// Create SHA-256 hash
const digest = await ExpoCrypto.digest(
ExpoCrypto.CryptoDigestAlgorithm.SHA256,
buffer
)
return Buffer.from(digest).toString('hex')
} catch (error) {
console.error('Error calculating file digest:', error)
throw error
Expand Down
Loading
Loading