diff --git a/package.json b/package.json index 96158c4..b3ece2a 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,8 @@ "webpack-serve": "3.2.0" }, "dependencies": { + "browser-encrypt-attachment": "^0.3.0", + "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.9", "matrix-js-sdk": "^4.0.0", "node-localstorage": "^2.1.5", diff --git a/src/components/chatbox.jsx b/src/components/chatbox.jsx index bb3ae0b..2e9f24e 100644 --- a/src/components/chatbox.jsx +++ b/src/components/chatbox.jsx @@ -341,8 +341,11 @@ class ChatBox extends React.Component { sender: event.getSender(), roomId: event.getRoomId(), content: event.getContent(), + } + console.log('message ===========>', message) + const messages = [...this.state.messages] messages.push(message) this.setState({ messages }) @@ -479,7 +482,7 @@ class ChatBox extends React.Component { { messages.map((message, index) => { return( - + ) }) } diff --git a/src/components/message.jsx b/src/components/message.jsx index 9a51e7d..08b67f5 100644 --- a/src/components/message.jsx +++ b/src/components/message.jsx @@ -1,44 +1,113 @@ import React from "react" import PropTypes from "prop-types" import Linkify from 'linkifyjs/react'; +import decryptFile from '../utils/decryptFile' -const Message = ({ message, userId, botId }) => { - const senderClass = () => { - switch (message.sender) { +class Message extends React.Component { + constructor(props) { + super(props); + this.state = { + decryptedUrl: null, + decryptedFile: null, + } + } + + componentDidMount() { + const needsDecryption = ['m.file', 'm.image']; + if (needsDecryption.includes(this.props.message.content.msgtype)) { + decryptFile(this.props.message.content.file, this.props.client) + .then((decryptedBlob) => { + const decryptedUrl = URL.createObjectURL(decryptedBlob) + this.setState({ + decryptedUrl: decryptedUrl, + decryptedBlob: decryptedBlob + }) + + }) + } + } + + componentWillUnmount() { + if (this.state.decryptedUrl) { + URL.revokeObjectURL(this.state.decryptedUrl); + } + } + + senderClass = () => { + switch (this.props.message.sender) { case 'from-me': return 'from-me' - case userId: + case this.props.userId: return 'from-me' - case botId: + case this.props.botId: return 'from-bot' default: return 'from-support' } } + renderTextMessage = () => { + const linkifyOpts = { + linkAttributes: { + rel: 'noreferrer noopener', + }, + } - if (message.content.formatted_body) { return ( -
-
+
+
+ { this.props.message.content.body } +
) } - const linkifyOpts = { - linkAttributes: { - rel: 'noreferrer noopener', - }, + renderHtmlMessage = () => { + return ( +
+
+
+ ) } - return ( -
-
- { message.content.body } + renderImageMessage = () => { + return ( + -
- ) + ) + } + + renderFileMessage = () => { + return ( + + ) + } + + render() { + console.log(this.props.message) + console.log(this.state) + const { message } = this.props; + + switch(message.content.msgtype) { + case 'm.file': + return this.renderFileMessage() + case 'm.image': + return this.renderImageMessage() + default: + if (message.content.formatted_body) { + return this.renderHtmlMessage() + } + return this.renderTextMessage() + } + } } export default Message; \ No newline at end of file diff --git a/src/test-helpers/index.js b/src/test-helpers/index.js deleted file mode 100644 index 4770ca9..0000000 --- a/src/test-helpers/index.js +++ /dev/null @@ -1,30 +0,0 @@ -function checkFunc(dom, selector) { - if (typeof dom.update === 'function') { - const el = dom.update().find(selector); - if (el.exists()) { - return el; - } - return null; - } - const els = dom.querySelectorAll(selector); - if (els.length !== 0) { - return els; - } - return null; -} - - -export async function waitForSelection(dom, selector) { - let numSleep = 0; - for (;;) { - const el = checkFunc(dom, selector); - if (el) { - return el; - } - if (numSleep > 2) { - throw new Error(`could not find ${selector}`); - } - await new Promise(resolve => setTimeout(resolve, 250)); - numSleep += 1; - } -} diff --git a/src/utils/decryptFile.js b/src/utils/decryptFile.js new file mode 100644 index 0000000..b5472fa --- /dev/null +++ b/src/utils/decryptFile.js @@ -0,0 +1,46 @@ +import encrypt from 'browser-encrypt-attachment'; +import 'isomorphic-fetch'; + +const ALLOWED_BLOB_MIMETYPES = { + 'image/jpeg': true, + 'image/gif': true, + 'image/png': true, + + 'video/mp4': true, + 'video/webm': true, + 'video/ogg': true, + + 'audio/mp4': true, + 'audio/webm': true, + 'audio/aac': true, + 'audio/mpeg': true, + 'audio/ogg': true, + 'audio/wave': true, + 'audio/wav': true, + 'audio/x-wav': true, + 'audio/x-pn-wav': true, + 'audio/flac': true, + 'audio/x-flac': true, +}; + +const decryptFile = (file, client) => { + const url = client.mxcUrlToHttp(file.url); + // Download the encrypted file as an array buffer. + return Promise.resolve(fetch(url)) + .then((response) => response.arrayBuffer()) + .then((responseData) => encrypt.decryptAttachment(responseData, file)) + .then((dataArray) => { + // IMPORTANT: we must not allow scriptable mime-types into Blobs otherwise + // they introduce XSS attacks if the Blob URI is viewed directly in the + // browser (e.g. by copying the URI into a new tab or window.) + let mimetype = file.mimetype ? file.mimetype.split(';')[0].trim() : ''; + if (!ALLOWED_BLOB_MIMETYPES[mimetype]) { + mimetype = 'application/octet-stream'; + } + + const blob = new Blob([dataArray], { type: mimetype }); + return blob; + }); +}; + +export default decryptFile; diff --git a/yarn.lock b/yarn.lock index 365a4b7..e997e3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3237,6 +3237,11 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-encrypt-attachment@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/browser-encrypt-attachment/-/browser-encrypt-attachment-0.3.0.tgz#205a94caadf0dc7e81413941812f655bd190ff1c" + integrity sha1-IFqUyq3w3H6BQTlBgS9lW9GQ/xw= + browser-process-hrtime@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" @@ -4789,6 +4794,13 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -6492,7 +6504,7 @@ humanize-url@^1.0.0: normalize-url "^1.0.0" strip-url-auth "^1.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -7051,7 +7063,7 @@ is-set@^2.0.1: resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -7140,6 +7152,14 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-fetch@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -8653,6 +8673,14 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-fetch@^2.3.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" @@ -12408,6 +12436,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.24" +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"