mirror of
https://github.com/Safe-Support-Chat/ocrcc-chatbox
synced 2024-11-01 00:55:26 +00:00
Merge pull request #1 from nomadic-labs/async_implementation
working on slow connections
This commit is contained in:
commit
a5759ab587
@ -16,7 +16,14 @@
|
|||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
"airbnb",
|
"airbnb",
|
||||||
"@babel/preset-env",
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"node": "12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"@babel/preset-react"
|
"@babel/preset-react"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
@ -274,7 +274,6 @@
|
|||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -22,7 +22,7 @@ import './styles.scss';
|
|||||||
|
|
||||||
const ENCRYPTION_CONFIG = { "algorithm": "m.megolm.v1.aes-sha2" };
|
const ENCRYPTION_CONFIG = { "algorithm": "m.megolm.v1.aes-sha2" };
|
||||||
const ENCRYPTION_NOTICE = "Messages in this chat are secured with end-to-end encryption."
|
const ENCRYPTION_NOTICE = "Messages in this chat are secured with end-to-end encryption."
|
||||||
const UNENCRYPTION_NOTICE = "End-to-end message encryption is not available on this browser."
|
const UNENCRYPTION_NOTICE = "Messages in this chat are not encrypted."
|
||||||
const RESTARTING_UNENCRYPTED_CHAT_MESSAGE = "Restarting chat without encryption."
|
const RESTARTING_UNENCRYPTED_CHAT_MESSAGE = "Restarting chat without encryption."
|
||||||
|
|
||||||
const DEFAULT_MATRIX_SERVER = "https://matrix.rhok.space/"
|
const DEFAULT_MATRIX_SERVER = "https://matrix.rhok.space/"
|
||||||
@ -56,6 +56,10 @@ class ChatBox extends React.Component {
|
|||||||
typingStatus: null,
|
typingStatus: null,
|
||||||
awaitingAgreement: true,
|
awaitingAgreement: true,
|
||||||
emojiSelectorOpen: false,
|
emojiSelectorOpen: false,
|
||||||
|
facilitatorInvited: false,
|
||||||
|
isMobile: true,
|
||||||
|
isSlowConnection: true,
|
||||||
|
decryptionErrors: {},
|
||||||
}
|
}
|
||||||
this.state = this.initialState
|
this.state = this.initialState
|
||||||
this.chatboxInput = React.createRef();
|
this.chatboxInput = React.createRef();
|
||||||
@ -63,6 +67,37 @@ class ChatBox extends React.Component {
|
|||||||
this.termsUrl = React.createRef();
|
this.termsUrl = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detectMobile = () => {
|
||||||
|
let isMobile = false;
|
||||||
|
|
||||||
|
if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
|
||||||
|
console.log('navigator.userAgent', navigator.userAgent)
|
||||||
|
isMobile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen.width < 767) {
|
||||||
|
console.log('screen.width', screen.width)
|
||||||
|
isMobile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ isMobile })
|
||||||
|
}
|
||||||
|
|
||||||
|
detectSlowConnection = () => {
|
||||||
|
let isSlowConnection = false;
|
||||||
|
|
||||||
|
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
||||||
|
|
||||||
|
if (typeof connection !== 'undefined' || connection === null) {
|
||||||
|
const connectionType = connection.effectiveType;
|
||||||
|
const slowConnections = ['slow-2g', '2g']
|
||||||
|
|
||||||
|
isSlowConnection = slowConnections.includes(connectionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ isSlowConnection })
|
||||||
|
}
|
||||||
|
|
||||||
handleToggleOpen = () => {
|
handleToggleOpen = () => {
|
||||||
this.setState((prev) => {
|
this.setState((prev) => {
|
||||||
let { showDock } = prev;
|
let { showDock } = prev;
|
||||||
@ -107,117 +142,120 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitChat = () => {
|
exitChat = async () => {
|
||||||
if (!this.state.client) return null;
|
if (!this.state.client) return null;
|
||||||
return this.state.client.leave(this.state.roomId)
|
|
||||||
.then(() => {
|
await this.state.client.leave(this.state.roomId)
|
||||||
const auth = {
|
|
||||||
type: 'm.login.password',
|
const auth = {
|
||||||
|
type: 'm.login.password',
|
||||||
|
user: this.state.userId,
|
||||||
|
identifier: {
|
||||||
|
type: "m.id.user",
|
||||||
user: this.state.userId,
|
user: this.state.userId,
|
||||||
identifier: {
|
},
|
||||||
type: "m.id.user",
|
password: this.state.password,
|
||||||
user: this.state.userId,
|
};
|
||||||
},
|
|
||||||
password: this.state.password,
|
await this.state.client.deactivateAccount(auth, true)
|
||||||
};
|
await this.state.client.stopClient()
|
||||||
this.state.client.deactivateAccount(auth, true)
|
await this.state.client.clearStores()
|
||||||
})
|
|
||||||
.then(() => this.state.client.stopClient())
|
this.state.localStorage.clear()
|
||||||
.then(() => this.state.client.clearStores())
|
this.setState(this.initialState)
|
||||||
.then(() => {
|
|
||||||
this.state.localStorage.clear()
|
|
||||||
this.setState(this.initialState)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeChat = () => {
|
createLocalStorage = async (deviceId, sessionId) => {
|
||||||
this.setState({ ready: false })
|
let localStorage = global.localStorage;
|
||||||
let client;
|
if (typeof localStorage === "undefined" || localStorage === null) {
|
||||||
|
const deviceDesc = `matrix-chat-${deviceId}-${sessionId}`
|
||||||
|
const localStoragePath = path.resolve(path.join(os.homedir(), ".local-storage", deviceDesc))
|
||||||
|
localStorage = new LocalStorage(localStoragePath);
|
||||||
|
}
|
||||||
|
return localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
createClientWithAccount = async () => {
|
||||||
|
const tmpClient = matrix.createClient(this.props.matrixServerUrl)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client = matrix.createClient(this.props.matrixServerUrl)
|
await tmpClient.registerRequest({})
|
||||||
} catch(error) {
|
} catch(err) {
|
||||||
console.log("Error creating client", error)
|
const username = uuid()
|
||||||
return this.handleInitError(err)
|
const password = uuid()
|
||||||
}
|
const sessionId = err.data.session
|
||||||
|
|
||||||
// empty registration request to get session
|
const account = await tmpClient.registerRequest({
|
||||||
return client.registerRequest({})
|
auth: {session: sessionId, type: "m.login.dummy"},
|
||||||
.then(data => {
|
inhibit_login: false,
|
||||||
console.log("Empty registration request to get session", data)
|
password: password,
|
||||||
|
username: username,
|
||||||
|
x_show_msisdn: true,
|
||||||
})
|
})
|
||||||
.catch(err => {
|
|
||||||
// actual registration request with randomly generated username and password
|
|
||||||
const username = uuid()
|
|
||||||
const password = uuid()
|
|
||||||
const sessionId = err.data.session
|
|
||||||
client.registerRequest({
|
|
||||||
auth: {session: sessionId, type: "m.login.dummy"},
|
|
||||||
inhibit_login: false,
|
|
||||||
password: password,
|
|
||||||
username: username,
|
|
||||||
x_show_msisdn: true,
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
|
|
||||||
// use node localStorage if window.localStorage is not available
|
const localStorage = await this.createLocalStorage(account.device_id, sessionId)
|
||||||
let localStorage = global.localStorage;
|
|
||||||
if (typeof localStorage === "undefined" || localStorage === null) {
|
|
||||||
const deviceDesc = `matrix-chat-${data.device_id}-${sessionId}`
|
|
||||||
const localStoragePath = path.resolve(path.join(os.homedir(), ".local-storage", deviceDesc))
|
|
||||||
localStorage = new LocalStorage(localStoragePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
accessToken: data.access_token,
|
accessToken: account.access_token,
|
||||||
userId: data.user_id,
|
userId: account.user_id,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
localStorage: localStorage,
|
localStorage: localStorage,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
deviceId: data.device_id,
|
deviceId: account.device_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
// create new client with full options
|
let opts = {
|
||||||
let opts = {
|
baseUrl: this.props.matrixServerUrl,
|
||||||
baseUrl: this.props.matrixServerUrl,
|
accessToken: account.access_token,
|
||||||
accessToken: data.access_token,
|
userId: account.user_id,
|
||||||
userId: data.user_id,
|
deviceId: account.device_id,
|
||||||
deviceId: data.device_id,
|
sessionStore: new matrix.WebStorageSessionStore(localStorage),
|
||||||
sessionStore: new matrix.WebStorageSessionStore(localStorage),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
client = matrix.createClient(opts)
|
return matrix.createClient(opts)
|
||||||
})
|
}
|
||||||
.catch(err => {
|
|
||||||
this.handleInitError(err)
|
|
||||||
})
|
|
||||||
.then(() => client.initCrypto())
|
|
||||||
.catch(err => {
|
|
||||||
client.stopClient()
|
|
||||||
client.clearStores()
|
|
||||||
return Promise.reject({ error: "Failed crypto", message: err })
|
|
||||||
})
|
|
||||||
.then(() => client.setDisplayName(this.props.anonymousDisplayName))
|
|
||||||
.then(() => client.startClient())
|
|
||||||
.then(() => {
|
|
||||||
this.setState({
|
|
||||||
client: client
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
if (err.error === "Failed crypto") {
|
|
||||||
this.initializeUnencryptedChat()
|
|
||||||
} else {
|
|
||||||
this.handleInitError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeUnencryptedChat = () => {
|
initializeChat = async () => {
|
||||||
this.setState({ ready: false })
|
this.setState({ ready: false })
|
||||||
|
|
||||||
|
const client = await this.createClientWithAccount()
|
||||||
|
this.setState({
|
||||||
|
client: client
|
||||||
|
})
|
||||||
|
client.setDisplayName(this.props.anonymousDisplayName)
|
||||||
|
this.setMatrixListeners(client)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.initCrypto()
|
||||||
|
} catch(err) {
|
||||||
|
return this.initializeUnencryptedChat()
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.startClient()
|
||||||
|
await this.createRoom(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeUnencryptedChat = async () => {
|
||||||
|
if (this.state.client) {
|
||||||
|
this.state.client.stopClient()
|
||||||
|
this.state.client.clearStores()
|
||||||
|
this.state.localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
ready: false,
|
||||||
|
facilitatorInvited: false,
|
||||||
|
decryptionErrors: {},
|
||||||
|
roomId: null,
|
||||||
|
typingStatus: null,
|
||||||
|
client: null,
|
||||||
|
isCryptoEnabled: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.displayBotMessage({ body: RESTARTING_UNENCRYPTED_CHAT_MESSAGE })
|
||||||
|
|
||||||
let opts = {
|
let opts = {
|
||||||
baseUrl: this.props.matrixServerUrl,
|
baseUrl: this.props.matrixServerUrl,
|
||||||
accessToken: this.state.accessToken,
|
accessToken: this.state.accessToken,
|
||||||
@ -226,20 +264,22 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
|
client = matrix.createClient(opts)
|
||||||
|
this.setState({
|
||||||
|
client: client,
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client = matrix.createClient(opts)
|
this.setMatrixListeners(client)
|
||||||
client.setDisplayName(this.props.anonymousDisplayName)
|
client.setDisplayName(this.props.anonymousDisplayName)
|
||||||
} catch {
|
await client.startClient()
|
||||||
return this.handleInitError(err)
|
await this.createRoom(client)
|
||||||
|
this.displayBotMessage({ body: UNENCRYPTION_NOTICE })
|
||||||
|
} catch(err) {
|
||||||
|
console.log("error", err)
|
||||||
|
this.handleInitError(err)
|
||||||
}
|
}
|
||||||
return client.startClient()
|
|
||||||
.then(() => {
|
|
||||||
this.setState({
|
|
||||||
client: client,
|
|
||||||
isCryptoEnabled: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => this.handleInitError(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInitError = (err) => {
|
handleInitError = (err) => {
|
||||||
@ -248,27 +288,36 @@ class ChatBox extends React.Component {
|
|||||||
this.setState({ ready: true })
|
this.setState({ ready: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDecryptionError = () => {
|
handleDecryptionError = async (event, err) => {
|
||||||
this.displayBotMessage({ body: RESTARTING_UNENCRYPTED_CHAT_MESSAGE })
|
if (this.state.client) {
|
||||||
|
const isCryptoEnabled = await this.state.client.isCryptoEnabled()
|
||||||
|
const isRoomEncrypted = this.state.client.isRoomEncrypted(this.state.roomId)
|
||||||
|
|
||||||
this.state.client.leave(this.state.roomId)
|
if (!isCryptoEnabled || !isRoomEncrypted) {
|
||||||
.then(() => this.state.client.stopClient())
|
return this.initializeUnencryptedChat()
|
||||||
.then(() => this.state.client.clearStores())
|
}
|
||||||
.then(() => this.initializeUnencryptedChat())
|
}
|
||||||
|
|
||||||
|
const eventId = event.getId()
|
||||||
|
this.displayFakeMessage({ body: '** Unable to decrypt message **' }, event.getSender(), eventId)
|
||||||
|
this.setState({ decryptionErrors: { [eventId]: true }})
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyAllRoomDevices = async function(roomId) {
|
verifyAllRoomDevices = async (client, room) => {
|
||||||
let room = this.state.client.getRoom(roomId);
|
if (!room) return;
|
||||||
|
if (!client) return;
|
||||||
|
if (!this.state.isCryptoEnabled) return;
|
||||||
|
|
||||||
let members = (await room.getEncryptionTargetMembers()).map(x => x["userId"])
|
let members = (await room.getEncryptionTargetMembers()).map(x => x["userId"])
|
||||||
let memberkeys = await this.state.client.downloadKeys(members);
|
let memberkeys = await client.downloadKeys(members);
|
||||||
for (const userId in memberkeys) {
|
for (const userId in memberkeys) {
|
||||||
for (const deviceId in memberkeys[userId]) {
|
for (const deviceId in memberkeys[userId]) {
|
||||||
await this.state.client.setDeviceVerified(userId, deviceId);
|
await client.setDeviceVerified(userId, deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoom = async function() {
|
createRoom = async (client) => {
|
||||||
const currentDate = new Date()
|
const currentDate = new Date()
|
||||||
const chatDate = currentDate.toLocaleDateString()
|
const chatDate = currentDate.toLocaleDateString()
|
||||||
const chatTime = currentDate.toLocaleTimeString()
|
const chatTime = currentDate.toLocaleTimeString()
|
||||||
@ -279,7 +328,7 @@ class ChatBox extends React.Component {
|
|||||||
name: `${chatTime}, ${chatDate} - ${this.props.roomName}`,
|
name: `${chatTime}, ${chatDate} - ${this.props.roomName}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCryptoEnabled = await this.state.client.isCryptoEnabled()
|
const isCryptoEnabled = await client.isCryptoEnabled()
|
||||||
|
|
||||||
if (isCryptoEnabled) {
|
if (isCryptoEnabled) {
|
||||||
roomConfig.initial_state = [
|
roomConfig.initial_state = [
|
||||||
@ -291,17 +340,9 @@ class ChatBox extends React.Component {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { room_id } = await this.state.client.createRoom(roomConfig)
|
const { room_id } = await client.createRoom(roomConfig)
|
||||||
|
|
||||||
this.state.client.setPowerLevel(room_id, this.props.botId, 100)
|
client.setPowerLevel(room_id, this.props.botId, 100)
|
||||||
|
|
||||||
if (isCryptoEnabled) {
|
|
||||||
this.verifyAllRoomDevices(room_id)
|
|
||||||
} else {
|
|
||||||
this.displayBotMessage({ body: UNENCRYPTION_NOTICE })
|
|
||||||
}
|
|
||||||
|
|
||||||
this.displayBotMessage({ body: this.props.confirmationMessage })
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
roomId: room_id,
|
roomId: room_id,
|
||||||
@ -310,6 +351,9 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendMessage = (message) => {
|
sendMessage = (message) => {
|
||||||
|
if (!this.state.client) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
this.state.client.sendTextMessage(this.state.roomId, message)
|
this.state.client.sendTextMessage(this.state.roomId, message)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
switch (err["name"]) {
|
switch (err["name"]) {
|
||||||
@ -328,10 +372,10 @@ class ChatBox extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
displayFakeMessage = (content, sender) => {
|
displayFakeMessage = (content, sender, messageId=uuid()) => {
|
||||||
const msgList = [...this.state.messages]
|
const msgList = [...this.state.messages]
|
||||||
const msg = {
|
const msg = {
|
||||||
id: uuid(),
|
id: messageId,
|
||||||
type: 'm.room.message',
|
type: 'm.room.message',
|
||||||
sender: sender,
|
sender: sender,
|
||||||
roomId: this.state.roomId,
|
roomId: this.state.roomId,
|
||||||
@ -343,7 +387,6 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
displayBotMessage = (content, roomId) => {
|
displayBotMessage = (content, roomId) => {
|
||||||
console.log('BOT MESSAGE', content)
|
|
||||||
const msgList = [...this.state.messages]
|
const msgList = [...this.state.messages]
|
||||||
const msg = {
|
const msg = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@ -353,7 +396,6 @@ class ChatBox extends React.Component {
|
|||||||
content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
msgList.push(msg)
|
msgList.push(msg)
|
||||||
console.log(msgList)
|
|
||||||
|
|
||||||
this.setState({ messages: msgList })
|
this.setState({ messages: msgList })
|
||||||
}
|
}
|
||||||
@ -375,9 +417,20 @@ class ChatBox extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for decryption error message and replace with decrypted message
|
||||||
|
// or push message to messages array
|
||||||
const messages = [...this.state.messages]
|
const messages = [...this.state.messages]
|
||||||
messages.push(message)
|
const decryptionErrors = {...this.state.decryptionErrors}
|
||||||
this.setState({ messages })
|
delete decryptionErrors[message.id]
|
||||||
|
const existingMessageIndex = messages.findIndex(({ id }) => id === message.id)
|
||||||
|
|
||||||
|
if (existingMessageIndex > -1) {
|
||||||
|
messages.splice(existingMessageIndex, 1, message)
|
||||||
|
} else {
|
||||||
|
messages.push(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ messages, decryptionErrors })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -394,53 +447,70 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMatrixListeners = client => {
|
||||||
|
client.once('sync', (state, prevState, res) => {
|
||||||
|
if (state === "PREPARED") {
|
||||||
|
this.setState({ ready: true })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("Room.timeline", (event, room) => {
|
||||||
|
if (event.getType() === "m.room.encryption") {
|
||||||
|
this.displayBotMessage({ body: ENCRYPTION_NOTICE }, room.room_id)
|
||||||
|
this.verifyAllRoomDevices(client, room)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getType() === "m.room.message" && !this.state.isCryptoEnabled) {
|
||||||
|
if (event.isEncrypted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.handleMessageEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getType() === "m.room.member" && event.getSender() === this.props.botId && event.getContent().membership === "invite") {
|
||||||
|
this.setState({ facilitatorInvited: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getType() === "m.room.member" && event.getSender() !== this.props.botId && event.getContent().membership === "join") {
|
||||||
|
this.verifyAllRoomDevices(client, room)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("Event.decrypted", (event, err) => {
|
||||||
|
if (err) {
|
||||||
|
return this.handleDecryptionError(event, err)
|
||||||
|
}
|
||||||
|
if (event.getType() === "m.room.message") {
|
||||||
|
this.handleMessageEvent(event)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("RoomMember.typing", (event, member) => {
|
||||||
|
if (member.typing && member.roomId === this.state.roomId) {
|
||||||
|
this.setState({ typingStatus: `${member.name} is typing...` })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({ typingStatus: null })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (this.state.client && prevState.client !== this.state.client) {
|
|
||||||
this.createRoom()
|
|
||||||
|
|
||||||
this.state.client.once('sync', (state, prevState, res) => {
|
|
||||||
if (state === "PREPARED") {
|
|
||||||
this.setState({ ready: true })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state.client.on("Room.timeline", (event, room, toStartOfTimeline) => {
|
|
||||||
if (event.getType() === "m.room.encryption") {
|
|
||||||
this.displayBotMessage({ body: ENCRYPTION_NOTICE }, room.room_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.getType() === "m.room.message" && !this.state.isCryptoEnabled) {
|
|
||||||
if (event.isEncrypted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.handleMessageEvent(event)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state.client.on("Event.decrypted", (event, err) => {
|
|
||||||
if (err) {
|
|
||||||
return this.handleDecryptionError()
|
|
||||||
}
|
|
||||||
if (event.getType() === "m.room.message") {
|
|
||||||
this.handleMessageEvent(event)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state.client.on("RoomMember.typing", (event, member) => {
|
|
||||||
if (member.typing && member.roomId === this.state.roomId) {
|
|
||||||
this.setState({ typingStatus: `${member.name} is typing...` })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.setState({ typingStatus: null })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevState.messages.length !== this.state.messages.length) {
|
if (prevState.messages.length !== this.state.messages.length) {
|
||||||
if (this.messageWindow.current.scrollTo) {
|
if (this.messageWindow.current.scrollTo) {
|
||||||
this.messageWindow.current.scrollTo(0, this.messageWindow.current.scrollHeight)
|
this.messageWindow.current.scrollTo(0, this.messageWindow.current.scrollHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!prevState.facilitatorInvited && this.state.facilitatorInvited) {
|
||||||
|
this.displayBotMessage({ body: this.props.confirmationMessage })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prevState.opened && this.state.opened) {
|
||||||
|
this.detectMobile()
|
||||||
|
// not sure what to do with this
|
||||||
|
// this.detectSlowConnection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -460,7 +530,11 @@ class ChatBox extends React.Component {
|
|||||||
|
|
||||||
handleAcceptTerms = () => {
|
handleAcceptTerms = () => {
|
||||||
this.setState({ awaitingAgreement: false })
|
this.setState({ awaitingAgreement: false })
|
||||||
this.initializeChat()
|
try {
|
||||||
|
this.initializeChat()
|
||||||
|
} catch(err) {
|
||||||
|
this.handleInitError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRejectTerms = () => {
|
handleRejectTerms = () => {
|
||||||
@ -474,7 +548,6 @@ class ChatBox extends React.Component {
|
|||||||
if (!Boolean(message)) return null;
|
if (!Boolean(message)) return null;
|
||||||
|
|
||||||
if (this.state.client && this.state.roomId) {
|
if (this.state.client && this.state.roomId) {
|
||||||
console.log("Setting state to empty")
|
|
||||||
this.setState({ inputValue: "" })
|
this.setState({ inputValue: "" })
|
||||||
this.chatboxInput.current.focus()
|
this.chatboxInput.current.focus()
|
||||||
return this.sendMessage(message)
|
return this.sendMessage(message)
|
||||||
@ -491,7 +564,7 @@ class ChatBox extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { ready, messages, inputValue, userId, roomId, typingStatus, opened, showDock, emojiSelectorOpen } = this.state;
|
const { ready, messages, inputValue, userId, roomId, typingStatus, opened, showDock, emojiSelectorOpen, isMobile, decryptionErrors } = this.state;
|
||||||
const inputLabel = 'Send a message...'
|
const inputLabel = 'Send a message...'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -532,11 +605,22 @@ class ChatBox extends React.Component {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
{ typingStatus &&
|
{ typingStatus &&
|
||||||
<div className="notices">
|
<div className="notices">
|
||||||
<div role="status">{typingStatus}</div>
|
<div role="status">{typingStatus}</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ Boolean(Object.keys(decryptionErrors).length) &&
|
||||||
|
<div className={`message from-bot`}>
|
||||||
|
<div className="text buttons">
|
||||||
|
{`Restart chat without encryption?`}
|
||||||
|
<button className="btn" id="accept" onClick={this.initializeUnencryptedChat}>RESTART</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{ !ready && <div className={`loader`}>loading...</div> }
|
{ !ready && <div className={`loader`}>loading...</div> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -553,12 +637,15 @@ class ChatBox extends React.Component {
|
|||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
ref={this.chatboxInput}
|
ref={this.chatboxInput}
|
||||||
/>
|
/>
|
||||||
<EmojiSelector
|
{
|
||||||
onEmojiClick={this.onEmojiClick}
|
(status === "entered") && !isMobile &&
|
||||||
emojiSelectorOpen={emojiSelectorOpen}
|
<EmojiSelector
|
||||||
toggleEmojiSelector={this.toggleEmojiSelector}
|
onEmojiClick={this.onEmojiClick}
|
||||||
closeEmojiSelector={this.closeEmojiSelector}
|
emojiSelectorOpen={emojiSelectorOpen}
|
||||||
/>
|
toggleEmojiSelector={this.toggleEmojiSelector}
|
||||||
|
closeEmojiSelector={this.closeEmojiSelector}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" value="Send" id="submit" onClick={this.handleSubmit} />
|
<input type="submit" value="Send" id="submit" onClick={this.handleSubmit} />
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user