Decode incoming images
This commit is contained in:
parent
c4230b8abc
commit
dd3efe0d61
8 changed files with 15923 additions and 1381 deletions
17156
package-lock.json
generated
17156
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
65
src/plugins/utils.js
Normal 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();
|
||||||
|
|
||||||
|
|
@ -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();
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue