Skip to content

Instantly share code, notes, and snippets.

@moeinrahimi
Last active June 20, 2023 03:35
Show Gist options
  • Save moeinrahimi/c1f30a94be6cbc7a0fd5a3eb14b7cb4c to your computer and use it in GitHub Desktop.
Save moeinrahimi/c1f30a94be6cbc7a0fd5a3eb14b7cb4c to your computer and use it in GitHub Desktop.
using matrix-js-sdk on react-native

there have been some compatibility issues on using matrix-sdk on react-native so I decided to create this gist for everyone who has struggled with. this tutorial shows you how to start with matrix on the latest version of react-native@0.60.4 and matrix-js-sdk@2.2.0, believe me with the latest version of matrix it is now easier to implement this on react-native.

install react-native by running

react-native init newproject --version react-native@0.60.4

now we need all these packages in order to set up matrix-js-sdk:

yarn add matrix-js-sdk buffer events fbemitter unorm url

since matrix-js-sdk's events implementation does not work on react-native we need to introduce an event emitter to it, I have written a wrapper for this cause, simply import it at top of your app.js before matrix-js-sdk, also I attached Matrix.js to this gist so you can simply use matrix without any headache, this demo has the ability to login,list rooms,join a room, send and receive messages.

NOTE: excuse me for bad ui since functionality is the number one priority and I don't have that free time create a clean ui, this is the best I can do for now.

matrix-js-sdk react-native demo

import React, { Component } from 'react';
import Promise from 'bluebird';
Promise.config({
warnings: false
});
import {
TextInput,
Button,
StyleSheet,
Text,
View,
FlatList,
TouchableHighlight,
ScrollView
} from 'react-native';
import axios from 'axios';
import './poly.js';
import sdk from 'matrix-js-sdk';
const baseUrl = 'https://msg.tebinja.com';
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
userName: 'dummyUser',
password: 'password',
userId: '',
accessToken: '',
client: '',
newRoom: '',
room_alias: 'not in a room',
nextSyncToken: '',
room_id: '',
roomToJoin: '',
memberList: [],
rooms: [],
hasRooms: false,
message: '',
messages: {},
isTyping: false,
displayName: ''
};
}
login = async e => {
const { userName, password } = this.state;
let url = baseUrl + '/_matrix/client/r0/login';
try {
let res = await axios.get(url);
let { data } = await axios.post(url, {
type: 'm.login.password',
user: userName,
password: password
});
let client = sdk.createClient({
baseUrl: baseUrl,
accessToken: data.access_token,
userId: data.user_id
});
let _this = this;
client.on('sync', function(state, prevState, data) {
try {
_this.setState({
nextSyncToken: data.nextSyncToken
});
switch (state) {
case 'PREPARED':
let roomList = client.getRooms();
_this.setState({ rooms: roomList });
break;
}
} catch (e) {
console.warn(e, 'sync');
}
});
client.on('Room.timeline', function(event, state) {
if (event.event.type == 'm.room.message') {
return _this.newMessage(event);
}
});
client.on('RoomMember.typing', function(event, member) {
var isTyping = member.typing;
console.log(member, event, 'typing');
if (isTyping) {
return _this.setState({
isTyping: { status: true, member: member }
});
}
return _this.setState({
isTyping: { status: false, member }
});
});
client.on('RoomMember.membership', function(event, member) {
if (member.membership === 'invite' && member.userId === _this.state.userId) {
client.joinRoom(member.roomId).done(function() {
let roomList = client.getRooms();
_this.setState({ rooms: roomList });
});
}
});
client.on('RoomState.members', function(event, state, member) {
var room = client.getRoom(state.roomId);
if (!room) {
return;
}
var memberList = state.getMembers();
if (memberList.length > 0) {
_this.setState({
memberList: memberList
});
}
});
client.startClient();
this.setState({
userId: data.user_id,
accessToken: data.access_token,
client: client
});
} catch (e) {
console.log(e, ' login request result');
console.warn(e.message, ' login request result');
}
};
getRoomMessages = async (roomId, nextSyncToken, accessToken) => {
try {
let allMessages =
baseUrl +
`/_matrix/client/r0/rooms/${roomId}/messages?limit=10&from=${nextSyncToken}&access_token=${accessToken}&dir=b`;
let result = await axios.get(allMessages);
return result.data;
} catch (error) {
console.log(error);
}
};
joinRoom = async (room)=>{
// e.preventDefault()
// console.warn(room,'aaaaaaaaaaa')
let {messages,roomToJoin,accessToken,client,nextSyncToken} = this.state
let url = baseUrl +`/_matrix/client/r0/join/${room.roomId}?access_token=${accessToken}`
try{
let {data} = await axios.post(url,
{
room_id : roomToJoin,
})
let roomMessages = messages[room.roomId]
// console.log(roomMessages , 'room messages',room.roomId)
if(roomMessages && roomMessages.length > 0){
let lastMsg = roomMessages[roomMessages.length-1]
// console.log(lastMsg , 'last msg')
// console.log(lastMsg.event,'aaaaaa')
// client.sendReadReceipt(lastMsg.event)
}
this.setState({
room_id:room.roomId,
room_alias : room.name,
})
}catch(e){
console.log(e)
}
}
sendMessage = async e => {
let { client, room_id, accessToken, message } = this.state;
try {
let content = {
msgtype: 'm.text',
body: message
};
let sentMessage = await client.sendMessage(room_id, content);
console.log(sentMessage, ' sent message ');
let url = `${baseUrl}/_matrix/client/r0/rooms/${room_id}/read_markers?access_token=${accessToken}`;
axios.post(url, {
'm.fully_read': sentMessage.event_id
});
client.sendTyping(room_id, false);
this.setState({
message: ''
});
} catch (e) {
console.log(e);
}
};
newMessage = async data => {
let { room_id, messages } = this.state;
console.warn(data, '---------');
let message = data.event.content;
if (!messages[data.event.room_id]) {
messages[data.event.room_id] = [];
}
message['sender'] = data.sender.name;
console.log(message['event'], 'new message', data);
// message['event'] = data.event
if (message.msgtype == 'm.image') {
message['type'] = 'image';
console.log(message.url, 'urlllllllll');
// let image = this.getFile(message.url)
// message['url'] = image
message['url'] = message.url;
}
messages[data.event.room_id].push(message);
console.log(messages);
this.setState({
messages: messages
});
};
getFile = mediaId => {
try {
mediaId = mediaId.replace('mxc://', '');
let url = baseUrl + `/_matrix/media/r0/download/${mediaId}`;
return url;
} catch (e) {
console.log(e);
}
};
handleChange = (stateKey, e) => {
this.setState({
[`${stateKey}`]: e
});
};
setTyping = e => {
this.handleChange('message', e);
let { client, room_id } = this.state;
client.sendTyping(room_id, true);
};
userTyping = () => {
let { isTyping, userId } = this.state;
if (isTyping.status == true && isTyping.member.userId != userId) {
return isTyping.member.name + ' is Typing';
}
};
render() {
let messages = this.state.messages[this.state.room_id]
? this.state.messages[this.state.room_id]
: [];
console.log(this.state.message, 'rooms list');
return (
<View>
<TextInput
style={{ height: 40 }}
placeholder="username"
onChangeText={e => this.handleChange('userName', e)}
/>
<TextInput
style={{ height: 40 }}
placeholder="password"
onChangeText={e => this.handleChange('password', e)}
/>
<Button onPress={this.login} title="LOGIN" />
<TextInput
style={{ height: 40 }}
placeholder="username"
onChangeText={e => this.handleChange('newRoom', e)}
/>
<Button onPress={this.createRoom} title="Add friend" />
<FlatList
data={this.state.rooms}
renderItem={({ item }) => (
<TouchableHighlight onPress={() => this.joinRoom(item)}>
<View key={item.roomId}>
<Text> {item.name} </Text>
</View>
</TouchableHighlight>
)}
/>
<Text> now in room: {this.state.room_alias} </Text>
<TextInput onChangeText={e => this.setTyping(e)} />
<Button onPress={this.sendMessage} title="send" />
<View style={StyleSheet.chat}>
<FlatList
data={messages}
renderItem={({ item }) => (
<ScrollView>
<Text> {item.body} </Text>
</ScrollView>
)}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
},
chat: {
height: 1000,
flex: 1
}
});
import { EventEmitter } from 'fbemitter';
import unorm from 'unorm'
String.prototype.normalize = function(form) { return require('unorm')[String(form).toLowerCase()](this); }
class Document {
constructor() {
this.emitter = new EventEmitter();
this.addEventListener = this.addEventListener.bind(this);
this.removeEventListener = this.removeEventListener.bind(this);
this._checkEmitter = this._checkEmitter.bind(this);
}
createElement(tagName) {
return {};
}
_checkEmitter() {
if (
!this.emitter ||
!(this.emitter.on || this.emitter.addEventListener || this.emitter.addListener)
) {
this.emitter = new EventEmitter();
}
}
addEventListener(eventName, listener) {
this._checkEmitter();
if (this.emitter.on) {
this.emitter.on(eventName, listener);
} else if (this.emitter.addEventListener) {
this.emitter.addEventListener(eventName, listener);
} else if (this.emitter.addListener) {
this.emitter.addListener(eventName, listener);
}
}
removeEventListener(eventName, listener) {
this._checkEmitter();
if (this.emitter.off) {
this.emitter.off(eventName, listener);
} else if (this.emitter.removeEventListener) {
this.emitter.removeEventListener(eventName, listener);
} else if (this.emitter.removeListener) {
this.emitter.removeListener(eventName, listener);
}
}
}
window.document = window.document || new Document();
@spaksa
Copy link

spaksa commented Sep 14, 2019

Doesn't work in Expo projects...

@moeinrahimi
Copy link
Author

@noodlesfw I have only tested this on building from source

@ozymand1as
Copy link

Have you encountered any problems with crashes on network state changes? I am using matrix js sdk in my project and it works fine for the most part, except for the fact that app instantly crashes when wifi is turned off / internet connection is lost.

@sparsh
Copy link

sparsh commented Oct 16, 2019

Thankyou for creating the walk through, it is really helpful. can you also help we with the code to make video call ?

They have documented chatting part but I could not find the part to make call using the js sdk

@chronikum
Copy link

Hi, thanks a lot for this walkthrough. I am struggling since days to implement XMPP (converse.js) in React Native and I will now look into matrix, I really hope it will work out. Does this run on both iOS/Android as it is running in the Javascript Engine?

@besionisrael
Copy link

Thank you for this gist. It's very usefull.
What's about sending images while chatting?

@ayberkanilatsiz
Copy link

Thanks for the gist.

Did you try to enable crypto?

@chakany
Copy link

chakany commented Jun 9, 2020

Should this work with Expo?

@moeinrahimi
Copy link
Author

@ayberkanilatsiz
no, I did not

@moeinrahimi
Copy link
Author

@chakerthehacker
at first, I tried to set up on expo but I had no luck and there was plenty of error.

@gillrohan
Copy link

Should this work with Expo?

worked for me without much of any problem

@moeinrahimi
Copy link
Author

@gillrohan
that's great news! I guess it's been easier to integrate since new versions.

@awahab994
Copy link

@moeinrahimi did you try to initiate the video or audio call with a matrix ?

@gauravsbagul
Copy link

Hi, I added the poly.js file and imported it into the App.js and now my release build is crashing on launch, but when I remove it build runs but I can't log in now.. any solution to this, please?

@ppulwey
Copy link

ppulwey commented Jul 16, 2021

Did anyone manage to get this running including Video Calls etc? Or is there another solution in the meantime?

@cherishwang6688
Copy link

cherishwang6688 commented Jun 20, 2023

matrix-js-sdk react-native demo

this link not work ,can not open the link, can you help me ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment