Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Fix message streaming using react-native-community/fetch #8

Merged
merged 6 commits into from Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -6,7 +6,7 @@ This is a work in progress to demonstrate importing [xmtp-js](https://github.com
This code should only be **used experimentally** as we work to remove our dependency on [PeculiarVentures/webcrypto](https://github.com/PeculiarVentures/webcrypto) (a SubtleCrypto polyfill) as the library includes the following warning.
>At this time this solution should be considered suitable for research and experimentation, further code and security review is needed before utilization in a production application.

## Usage
## Setup

1. Follow the [React Native guide](https://reactnative.dev/docs/environment-setup) to set up CLI environment
1. Clone this repo and `cd example_chat_rn`
Expand Down Expand Up @@ -34,14 +34,16 @@ This code should only be **used experimentally** as we work to remove our depend
- @azure/core-asynciterator-polyfill (necessary for Hermes only)
- @ethersproject/shims
- react-native-get-random-values
- [react-native-polyfill-globals](https://github.com/acostalima/react-native-polyfill-globals)
- crypto-browserify
- stream-browserify
- readable-stream
- https-browserify
- events
- process
- text-encoding
- web-streams-polyfill
- @peculiar/webcrypto (necessary for `SubtleCrypto` but need to remove)
- [@peculiar/webcrypto](https://github.com/PeculiarVentures/webcrypto) (necessary for `SubtleCrypto` but need to remove)
- assert
- os
- url
Expand Down
123 changes: 85 additions & 38 deletions components/Home.tsx
@@ -1,6 +1,7 @@
import React, {useCallback} from 'react';
import {useEffect, useState} from 'react';
import {
ActivityIndicator,
Alert,
Button,
Platform,
Expand All @@ -13,21 +14,24 @@ import {
} from 'react-native';

import {ethers, Signer} from 'ethers';
import {Client} from '@xmtp/xmtp-js';
import {Client, Conversation, DecodedMessage, Stream} from '@xmtp/xmtp-js';
import {useWalletConnect} from '@walletconnect/react-native-dapp';
import WalletConnectProvider from '@walletconnect/web3-provider';
import {utils} from '@noble/secp256k1';
import {Wallet} from 'ethers';

export const INFURA_API_KEY = '2bf116f1cc724c5ab9eec605ca8440e1';

export const RECIPIENT_ADDRESS = 'REPLACE_WITH_ETH_ADDRESS';

const Home = () => {
const [client, setClient] = useState<Client | undefined>(undefined);
const [signer, setSigner] = useState<Signer | undefined>(undefined);
const [client, setClient] = useState<Client>();
const [signer, setSigner] = useState<Signer>();
const [address, setAddress] = useState<string>('');
const [conversation, setConversation] = useState<Conversation>();
const [isLoading, setIsLoading] = useState<boolean>(false);

const connector = useWalletConnect();

const provider = new WalletConnectProvider({
infuraId: INFURA_API_KEY,
connector: connector,
Expand All @@ -45,6 +49,7 @@ const Home = () => {
};
requestSignatures();
} else {
setIsLoading(false);
if (!connector?.connected) {
return;
}
Expand All @@ -56,61 +61,99 @@ const Home = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connector]);

useEffect(() => {
const initXmtpClient = async () => {
if (!signer || client || conversation) {
setIsLoading(false);
return;
}
const xmtp = await Client.create(signer);
setClient(xmtp);
setIsLoading(false);

const newConversation = await xmtp.conversations.newConversation(
RECIPIENT_ADDRESS,
);
setConversation(newConversation);
};
initXmtpClient();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signer]);

useEffect(() => {
if (!client || !conversation) {
return;
}
let messageStream: Stream<DecodedMessage>;
const closeStream = async () => {
if (!messageStream) {
return;
}
await messageStream.return();
};
const startMessageStream = async () => {
closeStream();
messageStream = await conversation.streamMessages();
for await (const message of messageStream) {
if (message.senderAddress === client.address) {
continue
}
Alert.alert('Message received', message.content);
}
};
startMessageStream();
return () => {
closeStream();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [conversation]);

const connectWallet = useCallback(async () => {
setIsLoading(true);
await connector?.connect();
}, [connector]);

const generateWallet = useCallback(async () => {
setIsLoading(true);
const newSigner = new Wallet(utils.randomPrivateKey());
const newAddress = await newSigner.getAddress();
setAddress(newAddress);
setSigner(newSigner);
}, []);

const sendGm = React.useCallback(async () => {
if (!client) {
if (!conversation) {
return;
}
const conversation = await client.conversations.newConversation(
RECIPIENT_ADDRESS,
);
const message = await conversation.send(
`gm! ${Platform.OS === 'ios' ? 'from iOS' : 'from Android'}`,
);
Alert.alert('Message sent', message.content);
}, [client]);

useEffect(() => {
const initXmtpClient = async () => {
if (!signer) {
return;
}

const newAddress = await signer.getAddress();
setAddress(newAddress);

if (!client) {
/**
* Tip: Ethers' random wallet generation is slow in Hermes https://github.com/facebook/hermes/issues/626.
* If you would like to quickly create a random Wallet for testing, use:
* import {utils} from @noble/secp256k1;
* import {Wallet} from ethers;
* await Client.create(new Wallet(utils.randomPrivateKey()));
*/
const xmtp = await Client.create(signer);
setClient(xmtp);
}
};
initXmtpClient();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signer]);
}, [conversation]);

return (
<SafeAreaView>
<StatusBar />
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Example Chat App</Text>
<Text style={styles.sectionDescription}>
<Text style={styles.sectionDescription} selectable={true}>
{client ? address : 'Sign in with XMTP'}
</Text>
{client ? (
<Button title="Send a gm" onPress={sendGm} />
{isLoading ? (
<ActivityIndicator style={styles.spinner} />
) : (
<Button title="Sign in" onPress={connectWallet} />
<>
{client ? (
<Button title="Send gm" onPress={sendGm} />
) : (
<>
<Button title="Sign in" onPress={connectWallet} />
<Button title="Generate address" onPress={generateWallet} />
</>
)
}
</>
)}
</View>
</ScrollView>
Expand All @@ -133,6 +176,10 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '400',
},
spinner: {
justifyContent: 'center',
alignItems: 'center'
}
});

export default Home;
2 changes: 1 addition & 1 deletion metro.config.js
Expand Up @@ -16,7 +16,7 @@ module.exports = {
},
resolver: {
extraNodeModules: {
stream: require.resolve('stream-browserify'),
stream: require.resolve('readable-stream'),
crypto: require.resolve('crypto-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
Expand Down
7 changes: 6 additions & 1 deletion package.json
Expand Up @@ -18,8 +18,9 @@
"@react-navigation/native": "^6.0.13",
"@walletconnect/react-native-dapp": "^1.8.0",
"@walletconnect/web3-provider": "^1.8.0",
"@xmtp/xmtp-js": "^7.1.3",
"@xmtp/xmtp-js": "^7.6.0",
"assert": "^2.0.0",
"base-64": "^1.0.0",
"crypto-browserify": "^3.12.0",
"events": "^3.3.0",
"expo": "^47.0.0",
Expand All @@ -31,8 +32,12 @@
"process": "^0.11.10",
"react": "18.1.0",
"react-native": "0.70.1",
"react-native-fetch-api": "^3.0.0",
"react-native-get-random-values": "^1.8.0",
"react-native-polyfill-globals": "^3.1.0",
"react-native-svg": "9.6.4",
"react-native-url-polyfill": "^1.3.0",
"readable-stream": "^4.2.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"text-encoding": "^0.7.0",
Expand Down
13 changes: 13 additions & 0 deletions patches/react-native-fetch-api+3.0.0.patch
@@ -0,0 +1,13 @@
diff --git a/node_modules/react-native-fetch-api/src/Fetch.js b/node_modules/react-native-fetch-api/src/Fetch.js
index b3a5614..4c7c9b3 100644
--- a/node_modules/react-native-fetch-api/src/Fetch.js
+++ b/node_modules/react-native-fetch-api/src/Fetch.js
@@ -31,7 +31,7 @@ function createStream(cancel) {

class Fetch {
_nativeNetworkSubscriptions = new Set();
- _nativeResponseType = "blob";
+ _nativeResponseType = "text";
_nativeRequestHeaders = {};
_nativeResponseHeaders = {};
_nativeRequestTimeout = 0;
2 changes: 1 addition & 1 deletion polyfills.js
Expand Up @@ -5,8 +5,8 @@ import 'react-native-get-random-values';
import '@ethersproject/shims';

import 'text-encoding';
import 'web-streams-polyfill';
import '@azure/core-asynciterator-polyfill';
import 'react-native-polyfill-globals/auto';

// Necessary for @peculiar/webcrypto.
if (!global.Buffer) {
Expand Down