Decode incoming images

This commit is contained in:
N-Pex 2020-11-19 17:08:58 +01:00
parent c4230b8abc
commit dd3efe0d61
8 changed files with 15923 additions and 1381 deletions

17156
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,10 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"aes-js": "^3.1.2",
"axios": "^0.21.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"json-web-key": "^0.4.0",
"material-design-icons-iconfont": "^5.0.1", "material-design-icons-iconfont": "^5.0.1",
"matrix-js-sdk": "^9.0.1", "matrix-js-sdk": "^9.0.1",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",

View file

@ -116,6 +116,10 @@ $chat-text-size: 0.7pt;
border-radius: 10px 10px 0 10px; border-radius: 10px 10px 0 10px;
padding: 8px; padding: 8px;
} }
.bubble.image-bubble {
padding: 0px;
overflow: hidden;
}
.message { .message {
color: white; color: white;
} }
@ -134,6 +138,10 @@ $chat-text-size: 0.7pt;
border-style: solid !important; border-style: solid !important;
border-color: #cccccc !important; border-color: #cccccc !important;
} }
.bubble.image-bubble {
padding: 0px;
overflow: hidden;
}
.avatar { .avatar {
position: absolute; position: absolute;
left: -10px; left: -10px;

View file

@ -6,7 +6,7 @@
style="overflow-x: hidden; overflow-y: auto" style="overflow-x: hidden; overflow-y: auto"
v-on:scroll="onScroll" v-on:scroll="onScroll"
> >
<div v-for="event in events" :key="event.eventId"> <div v-for="event in events" :key="event.getId()">
<component <component
:is="componentForEvent(event)" :is="componentForEvent(event)"
:room="room" :room="room"

View file

@ -2,6 +2,9 @@
<div> <div>
<div class="messageIn"> <div class="messageIn">
<div class="sender">{{ messageEventDisplayName(event) }}</div> <div class="sender">{{ messageEventDisplayName(event) }}</div>
<div class="bubble image-bubble">
<v-img :aspect-ratio="16 / 9" ref="image" :src="src" cover />
</div>
<v-avatar class="avatar" size="40" color="grey"> <v-avatar class="avatar" size="40" color="grey">
<img <img
v-if="messageEventAvatar(event)" v-if="messageEventAvatar(event)"
@ -11,10 +14,6 @@
messageEventDisplayName(event).substring(0, 1).toUpperCase() messageEventDisplayName(event).substring(0, 1).toUpperCase()
}}</span> }}</span>
</v-avatar> </v-avatar>
<div class="bubble">
<v-img :aspect-ratio="16 / 9" ref="image" :src="src" contain />
</div>
</div> </div>
<div class="time"> <div class="time">
{{ formatTime(event.event.origin_server_ts) }} {{ formatTime(event.event.origin_server_ts) }}
@ -24,6 +23,8 @@
<script> <script>
import messageMixin from "./messageMixin"; import messageMixin from "./messageMixin";
//import axios from 'axios';
import util from "../../plugins/utils";
export default { export default {
mixins: [messageMixin], mixins: [messageMixin],
@ -33,23 +34,23 @@ export default {
}; };
}, },
mounted() { mounted() {
// const width = this.$refs.image.$el.clientWidth; console.log("Mounted with event:", JSON.stringify(this.event.getContent()));
// const height = (width * 9) / 16; const width = this.$refs.image.$el.clientWidth;
const content = this.event.getContent(); const height = (width * 9) / 16;
if ( util
content && .getThumbnail(this.$matrix.matrixClient, this.event, width, height)
content.info && .then((url) => {
content.info.thumbnail_file && this.src = url;
content.info.thumbnail_file.url })
) { .catch((err) => {
this.src = this.$matrix.matrixClient.mxcUrlToHttp( console.log("Failed to fetch thumbnail: ", err);
content.info.thumbnail_file.url, });
content.info.w, },
content.info.h, beforeDestroy() {
"scale", if (this.src) {
true const objectUrl = this.src;
); this.src = null;
console.log("SRC set to: ", this.src); URL.revokeObjectURL(objectUrl);
} }
}, },
}; };

View file

@ -2,8 +2,8 @@
<div> <div>
<div class="messageOut"> <div class="messageOut">
<div class="sender">{{ "You" }}</div> <div class="sender">{{ "You" }}</div>
<div class="bubble"> <div class="bubble image-bubble">
<v-img :aspect-ratio="16/9" ref="image" :src="src" contain /> <v-img :aspect-ratio="16/9" ref="image" :src="src" cover />
</div> </div>
<div class="status">{{ event.status }}</div> <div class="status">{{ event.status }}</div>
</div> </div>
@ -27,6 +27,13 @@ export default {
const width = this.$refs.image.$el.clientWidth; const width = this.$refs.image.$el.clientWidth;
const height = (width * 9) / 16; const height = (width * 9) / 16;
this.src = this.$matrix.matrixClient.mxcUrlToHttp(this.event.getContent().url, width, height, 'scale', false); this.src = this.$matrix.matrixClient.mxcUrlToHttp(this.event.getContent().url, width, height, 'scale', false);
},
beforeDestroy() {
if (this.src) {
const objectUrl = this.src;
this.src = null;
URL.revokeObjectURL(objectUrl);
}
} }
}; };
</script> </script>

65
src/plugins/utils.js Normal file
View file

@ -0,0 +1,65 @@
import axios from 'axios';
class Util {
getThumbnail(matrixClient, event, ignoredw, ignoredh) {
return new Promise((resolve, reject) => {
const content = event.getContent();
var url = null;
var file = null;
if (
content &&
content.info &&
content.info.thumbnail_file &&
content.info.thumbnail_file.url
) {
file = content.info.thumbnail_file;
// var width = w;
// var height = h;
// if (content.info.w < w || content.info.h < h) {
// width = content.info.w;
// height = content.info.h;
// }
// url = matrixClient.mxcUrlToHttp(
// file.url,
// width, height,
// "scale",
// true
// );
url = matrixClient.mxcUrlToHttp(file.url);
} else if (content.file && content.file.url) {
// No thumb, use real url
file = content.file;
url = matrixClient.mxcUrlToHttp(file.url);
}
if (url == null) {
reject("No url found!");
}
axios.get(url, { responseType: 'arraybuffer' })
.then(response => {
return new Promise((resolve, ignoredReject) => {
var aesjs = require('aes-js');
//var JSONWebKey = require( 'json-web-key' );
var base64Url = require('json-web-key/lib/base64url');
//var tou8 = require('buffer-to-uint8array');
var key = base64Url.decode(file.key.k);
var iv = base64Url.decode(file.iv);
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(iv));
const data = new Uint8Array(response.data);
var decryptedBytes = aesCtr.decrypt(data);
resolve(decryptedBytes);
});
})
.then(bytes => {
resolve(URL.createObjectURL(new Blob([bytes.buffer], { type: 'image/png' })));
})
.catch(err => {
console.log("Download error: ", err);
reject(err);
});
});
}
}
export default new Util();

View file

@ -1,16 +0,0 @@
class Util {
readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
resolve(e.target.result as ArrayBuffer);
};
reader.onerror = function(e) {
reject(e);
};
reader.readAsArrayBuffer(file);
})
};
};
export default new Util();