Support for running proof check on client side
This commit is contained in:
parent
66eef037e0
commit
46479d4c37
6 changed files with 126 additions and 29 deletions
61
src/components/file_mode/EventAttachmentInfo.vue
Normal file
61
src/components/file_mode/EventAttachmentInfo.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="attachment-info">
|
||||
<div v-if="loadingProof">
|
||||
<div style="font-size: 0.7em; opacity: 0.7">
|
||||
<v-progress-circular indeterminate class="mb-0"></v-progress-circular>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<C2PAInfo class="attachment-info__detail-box" v-if="hasC2PA" :flags="attachment?.proofHintFlags" />
|
||||
<EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment?.proof?.integrity?.exif" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import C2PAInfo from "../content-credentials/C2PAInfo.vue";
|
||||
import EXIFInfo from "../content-credentials/EXIFInfo.vue";
|
||||
import { computed, onMounted, Ref, ref } from "vue";
|
||||
import { EventAttachment } from "../../models/eventAttachment";
|
||||
import proofmode from "../../plugins/proofmode";
|
||||
import { extractProofHintFlags } from "../../models/proof";
|
||||
|
||||
const { attachment } = defineProps<{
|
||||
attachment?: EventAttachment;
|
||||
}>();
|
||||
|
||||
const loadingProof: Ref<boolean> = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
if (attachment?.proofHintFlags && attachment.proof === undefined) {
|
||||
const a = attachment;
|
||||
loadingProof.value = true;
|
||||
a.loadSrc()
|
||||
.then((data) => {
|
||||
if (data && data.data) {
|
||||
return proofmode.proofCheckSource(data.data).then((res) => {
|
||||
a.proof = res;
|
||||
a.proofHintFlags = extractProofHintFlags(a.proof);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loadingProof.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const hasC2PA = computed(() => {
|
||||
return attachment?.proof?.integrity?.c2pa !== undefined;
|
||||
});
|
||||
|
||||
const hasExif = computed(() => {
|
||||
return attachment?.proof?.integrity?.exif !== undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/assets/css/chat.scss" as *;
|
||||
@use "@/assets/css/sendattachments.scss" as *;
|
||||
</style>
|
||||
|
|
@ -5,62 +5,76 @@
|
|||
<v-icon @click.stop="$emit('close')" color="white" class="clickable">arrow_back</v-icon>
|
||||
<div class="room-name no-upper">{{ displayDate }}</div>
|
||||
<div>
|
||||
<v-icon v-if="showInfoButton" color="white" class="clickable">info_outline</v-icon>
|
||||
<v-icon @click.stop="showInfo = true" v-if="showInfoButton" color="white" class="clickable"
|
||||
>info_outline</v-icon
|
||||
>
|
||||
<v-icon @click.stop="showMoreMenu = true" color="white" class="clickable">more_vert</v-icon>
|
||||
</div>
|
||||
</v-container>
|
||||
</div>
|
||||
|
||||
<div class="gallery-current-item">
|
||||
<ThumbnailView :item="items[currentItemIndex]" />
|
||||
<ThumbnailView :item="currentAttachment" />
|
||||
<div class="download-button clickable" @click.stop="downloadOne">
|
||||
<v-icon color="black">arrow_downward</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery-thumbnail-container">
|
||||
<div
|
||||
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
|
||||
@click="currentItemIndex = id"
|
||||
v-for="(currentImageInput, id) in items"
|
||||
:key="id"
|
||||
:class="{ 'file-drop-thumbnail': true, clickable: true, current: currentAttachment == attachment }"
|
||||
@click="currentAttachment = attachment"
|
||||
v-for="(attachment, index) in items"
|
||||
:key="index"
|
||||
>
|
||||
<v-img
|
||||
v-if="currentImageInput"
|
||||
:src="currentImageInput.thumbnail ? currentImageInput.thumbnail : currentImageInput.src"
|
||||
v-if="attachment"
|
||||
:src="attachment.thumbnail ? attachment.thumbnail : attachment.src"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MORE MENU POPUP -->
|
||||
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" :showProfile="false" @close="showMoreMenu = false" />
|
||||
<v-bottom-sheet v-model="showInfo" theme="dark" height="80%">
|
||||
<v-card class="text-center send-attachments-info-popup">
|
||||
<v-card-title class="d-flex flex-column pa-0">
|
||||
<div class="align-self-end done-button clickable" @click="showInfo = false">{{ $t("menu.done") }}</div>
|
||||
<v-divider />
|
||||
</v-card-title>
|
||||
<v-card-title class="d-flex"> </v-card-title>
|
||||
<v-card-text>
|
||||
<EventAttachmentInfo :attachment="currentAttachment" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-bottom-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import MoreMenuPopup from "../MoreMenuPopup.vue";
|
||||
import util, { CLIENT_EVENT_PROOF_HINT } from "../../plugins/utils";
|
||||
import util from "../../plugins/utils";
|
||||
import ThumbnailView from "./ThumbnailView.vue";
|
||||
import { EventAttachment, KeanuEvent } from "../../models/eventAttachment";
|
||||
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, watch } from "vue";
|
||||
import EventAttachmentInfo from "./EventAttachmentInfo.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const $matrix: any = inject("globalMatrix");
|
||||
|
||||
const props = defineProps<{ originalEvent: KeanuEvent, items: EventAttachment[]; initialItem: EventAttachment | undefined }>();
|
||||
const props = defineProps<{
|
||||
originalEvent: KeanuEvent;
|
||||
items: EventAttachment[];
|
||||
initialItem: EventAttachment | undefined;
|
||||
}>();
|
||||
|
||||
const currentItemIndex: Ref<number> = ref(0);
|
||||
const currentAttachment: Ref<EventAttachment | undefined> = ref(undefined);
|
||||
const showMoreMenu: Ref<boolean> = ref(false);
|
||||
|
||||
const showInfo: Ref<boolean> = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
document.body.classList.add("dark");
|
||||
if (props.initialItem) {
|
||||
currentItemIndex.value = props.items.findIndex((v) => v === props.initialItem);
|
||||
if (currentItemIndex.value < 0) {
|
||||
currentItemIndex.value = 0;
|
||||
}
|
||||
}
|
||||
currentAttachment.value = props.initialItem ? props.initialItem : props.items[0];
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
|
@ -72,11 +86,7 @@ const displayDate = computed(() => {
|
|||
});
|
||||
|
||||
const showInfoButton = computed(() => {
|
||||
const item = currentItemIndex.value >= 0 && currentItemIndex.value < props.items.length ? props.items[currentItemIndex.value] : undefined;
|
||||
if (item) {
|
||||
return item.event.getContent()[CLIENT_EVENT_PROOF_HINT] !== undefined;
|
||||
}
|
||||
return false;
|
||||
return currentAttachment.value?.proofHintFlags !== undefined;
|
||||
});
|
||||
|
||||
const moreMenuItems = computed(() => {
|
||||
|
|
@ -94,15 +104,15 @@ const moreMenuItems = computed(() => {
|
|||
watch(props.items, (newValue: EventAttachment[], oldValue: EventAttachment[]) => {
|
||||
// Added or removed?
|
||||
if (newValue && oldValue && newValue.length > oldValue.length) {
|
||||
currentItemIndex.value = oldValue.length;
|
||||
currentAttachment.value = newValue[oldValue.length];
|
||||
} else if (newValue) {
|
||||
currentItemIndex.value = newValue.length - 1;
|
||||
currentAttachment.value = newValue[newValue.length - 1];
|
||||
}
|
||||
});
|
||||
|
||||
const downloadOne = () => {
|
||||
if (currentItemIndex.value >= 0 && currentItemIndex.value < props.items.length) {
|
||||
util.download($matrix.matrixClient, $matrix.useAuthedMedia, props.items[currentItemIndex.value].event);
|
||||
if (currentAttachment.value) {
|
||||
util.download($matrix.matrixClient, $matrix.useAuthedMedia, currentAttachment.value.event);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
import proofmode from "../plugins/proofmode";
|
||||
import imageResize from "image-resize";
|
||||
import { computed, ref, Ref, shallowReactive, unref } from "vue";
|
||||
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT } from "@/plugins/utils";
|
||||
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, CLIENT_EVENT_PROOF_HINT } from "@/plugins/utils";
|
||||
import { extractProofHintFlags } from "./proof";
|
||||
|
||||
export class AttachmentManager {
|
||||
|
|
@ -220,6 +220,8 @@ export class AttachmentManager {
|
|||
|
||||
const fileSize = this.getSrcFileSize(event);
|
||||
|
||||
let proofHintFlags = event.getContent()[CLIENT_EVENT_PROOF_HINT];
|
||||
|
||||
const attachment: EventAttachment = {
|
||||
event: event,
|
||||
name: this.getFileName(event),
|
||||
|
|
@ -227,6 +229,8 @@ export class AttachmentManager {
|
|||
srcProgress: -1,
|
||||
thumbnailProgress: -1,
|
||||
autoDownloadable: fileSize <= this.maxSizeAutoDownloads,
|
||||
proofHintFlags: proofHintFlags ? JSON.parse(proofHintFlags) : proofHintFlags,
|
||||
proof: undefined,
|
||||
loadSrc: () => Promise.reject("Not implemented"),
|
||||
loadThumbnail: () => Promise.reject("Not implemented"),
|
||||
loadBlob: () => Promise.reject("Not implemented"),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { MatrixEvent, Room } from "matrix-js-sdk";
|
||||
import { AttachmentBatch } from "./attachment";
|
||||
import { Proof, ProofHintFlags } from "./proof";
|
||||
|
||||
export type KeanuEventExtension = {
|
||||
isMxThread?: boolean;
|
||||
|
|
@ -26,6 +27,8 @@ export type EventAttachment = {
|
|||
thumbnailProgress: number;
|
||||
thumbnailPromise?: Promise<EventAttachmentUrlData>;
|
||||
autoDownloadable: boolean;
|
||||
proof?: Proof;
|
||||
proofHintFlags?: ProofHintFlags;
|
||||
loadSrc: () => Promise<EventAttachmentUrlData>;
|
||||
loadThumbnail: () => Promise<EventAttachmentUrlData>;
|
||||
loadBlob: () => Promise<{data: Blob}>;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,20 @@ class ProofMode {
|
|||
const worker = await this.getProofcheckWorker();
|
||||
const res = await worker.checkFiles([file]);
|
||||
if (res && res.files && res.files.length == 1) {
|
||||
delete res.files[0].data; // Don't need to hang on to the data!
|
||||
return res.files[0];
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async proofCheckSource(src: string): Promise<ProofCheckResult | undefined> {
|
||||
try {
|
||||
const worker = await this.getProofcheckWorker();
|
||||
const res = await worker.checkURLs([src]);
|
||||
if (res && res.files && res.files.length == 1) {
|
||||
delete res.files[0].data; // Don't need to hang on to the data!
|
||||
return res.files[0];
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -35,4 +49,6 @@ class ProofMode {
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new ProofMode();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Observable, Subject } from "threads/observable";
|
||||
import { expose } from "threads/worker";
|
||||
import { checkFiles } from "@guardianproject/proofmode";
|
||||
import { checkFiles, checkURLs } from "@guardianproject/proofmode";
|
||||
|
||||
let subject = new Subject();
|
||||
|
||||
|
|
@ -12,6 +12,9 @@ const check = {
|
|||
checkFiles: (files) => {
|
||||
return checkFiles(files, sendMessage);
|
||||
},
|
||||
checkURLs: (urls) => {
|
||||
return checkURLs(urls, sendMessage);
|
||||
},
|
||||
values: () => {
|
||||
return Observable.from(subject);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue