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"
},
"dependencies": {
"aes-js": "^3.1.2",
"axios": "^0.21.0",
"core-js": "^3.6.5",
"json-web-key": "^0.4.0",
"material-design-icons-iconfont": "^5.0.1",
"matrix-js-sdk": "^9.0.1",
"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;
padding: 8px;
}
.bubble.image-bubble {
padding: 0px;
overflow: hidden;
}
.message {
color: white;
}
@ -134,6 +138,10 @@ $chat-text-size: 0.7pt;
border-style: solid !important;
border-color: #cccccc !important;
}
.bubble.image-bubble {
padding: 0px;
overflow: hidden;
}
.avatar {
position: absolute;
left: -10px;

View file

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

View file

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

View file

@ -2,8 +2,8 @@
<div>
<div class="messageOut">
<div class="sender">{{ "You" }}</div>
<div class="bubble">
<v-img :aspect-ratio="16/9" ref="image" :src="src" contain />
<div class="bubble image-bubble">
<v-img :aspect-ratio="16/9" ref="image" :src="src" cover />
</div>
<div class="status">{{ event.status }}</div>
</div>
@ -27,6 +27,13 @@ export default {
const width = this.$refs.image.$el.clientWidth;
const height = (width * 9) / 16;
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>

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();