Merge branch 'signature-app/object-version' into 'signature-app-master'

Add versioning to stored objects

See merge request Chill-Projet/chill-bundles!710
This commit is contained in:
2024-09-04 12:46:43 +00:00
369 changed files with 3687 additions and 1252 deletions

View File

@@ -1,5 +1,5 @@
import {makeFetch} from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import {PostStoreObjectSignature} from "../../types";
import {PostStoreObjectSignature, StoredObject} from "../../types";
const algo = 'AES-CBC';
@@ -21,11 +21,22 @@ const createFilename = (): string => {
return text;
};
export const uploadFile = async (uploadFile: ArrayBuffer): Promise<string> => {
/**
* Fetches a new stored object from the server.
*
* @async
* @function fetchNewStoredObject
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
*/
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
return makeFetch("POST", '/api/1.0/doc-store/stored-object/create', null);
}
export const uploadVersion = async (uploadFile: ArrayBuffer, storedObject: StoredObject): Promise<string> => {
const params = new URLSearchParams();
params.append('expires_delay', "180");
params.append('submit_delay', "180");
const asyncData: PostStoreObjectSignature = await makeFetch("GET", URL_POST + "?" + params.toString());
const asyncData: PostStoreObjectSignature = await makeFetch("GET", `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + "?" + params.toString());
const suffix = createFilename();
const filename = asyncData.prefix + suffix;
const formData = new FormData();
@@ -50,7 +61,6 @@ export const uploadFile = async (uploadFile: ArrayBuffer): Promise<string> => {
}
export const encryptFile = async (originalFile: ArrayBuffer): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
console.log('encrypt', originalFile);
const iv = crypto.getRandomValues(new Uint8Array(16));
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ]);
const exportedKey = await window.crypto.subtle.exportKey('jwk', key);

View File

@@ -1,7 +1,7 @@
import {CollectionEventPayload} from "../../../../../ChillMainBundle/Resources/public/module/collection";
import {createApp} from "vue";
import DropFileWidget from "../../vuejs/DropFileWidget/DropFileWidget.vue"
import {StoredObject, StoredObjectCreated} from "../../types";
import {StoredObject, StoredObjectVersion} from "../../types";
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
const i18n = _createI18n({});
@@ -30,15 +30,17 @@ const startApp = (divElement: HTMLDivElement, collectionEntry: null|HTMLLIElemen
DropFileWidget,
},
methods: {
addDocument: function(object: StoredObjectCreated): void {
console.log('object added', object);
this.$data.existingDoc = object;
input_stored_object.value = JSON.stringify(object);
addDocument: function({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void {
console.log('object added', stored_object);
console.log('version added', stored_object_version);
this.$data.existingDoc = stored_object;
this.$data.existingDoc.currentVersion = stored_object_version;
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
},
removeDocument: function(object: StoredObject): void {
console.log('catch remove document', object);
input_stored_object.value = "";
this.$data.existingDoc = null;
this.$data.existingDoc = undefined;
console.log('collectionEntry', collectionEntry);
if (null !== collectionEntry) {

View File

@@ -1,36 +1,51 @@
import {DateTime} from "../../../ChillMainBundle/Resources/public/types";
import {DateTime, User} from "../../../ChillMainBundle/Resources/public/types";
export type StoredObjectStatus = "ready"|"failure"|"pending";
export type StoredObjectStatus = "empty"|"ready"|"failure"|"pending";
export interface StoredObject {
id: number,
/**
* filename of the object in the object storage
*/
filename: string,
creationDate: DateTime,
datas: object,
iv: number[],
keyInfos: object,
title: string,
type: string,
uuid: string,
status: StoredObjectStatus,
id: number,
title: string|null,
uuid: string,
prefix: string,
status: StoredObjectStatus,
currentVersion: null|StoredObjectVersionCreated|StoredObjectVersionPersisted,
totalVersions: number,
datas: object,
/** @deprecated */
creationDate: DateTime,
createdAt: DateTime|null,
createdBy: User|null,
_permissions: {
canEdit: boolean,
canSee: boolean,
},
_links?: {
dav_link?: {
href: string
expiration: number
},
}
dav_link?: {
href: string
expiration: number
},
},
}
export interface StoredObjectCreated {
status: "stored_object_created",
filename: string,
iv: Uint8Array,
keyInfos: object,
type: string,
export interface StoredObjectVersion {
/**
* filename of the object in the object storage
*/
filename: string,
iv: number[],
keyInfos: JsonWebKey,
type: string,
}
export interface StoredObjectVersionCreated extends StoredObjectVersion {
persisted: false,
}
export interface StoredObjectVersionPersisted extends StoredObjectVersionCreated {
version: number,
id: number,
createdAt: DateTime|null,
createdBy: User|null,
}
export interface StoredObjectStatusChange {
@@ -82,4 +97,4 @@ export interface Signature {
zones: SignatureZone[],
}
export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error';
export type SignedState = 'pending' | 'signed' | 'rejected' | 'canceled' | 'error';

View File

@@ -1,20 +1,20 @@
<template>
<div v-if="'ready' === props.storedObject.status || 'stored_object_created' === props.storedObject.status" class="btn-group">
<div v-if="isButtonGroupDisplayable" class="btn-group">
<button :class="Object.assign({'btn': true, 'btn-outline-primary': true, 'dropdown-toggle': true, 'btn-sm': props.small})" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type) && props.storedObject.status !== 'stored_object_created'">
<li v-if="isEditableOnline">
<wopi-edit-button :stored-object="props.storedObject" :classes="{'dropdown-item': true}" :execute-before-leave="props.executeBeforeLeave"></wopi-edit-button>
</li>
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type) && props.davLink !== undefined && props.davLinkExpiration !== undefined">
<li v-if="isEditableOnDesktop">
<desktop-edit-button :classes="{'dropdown-item': true}" :edit-link="props.davLink" :expiration-link="props.davLinkExpiration"></desktop-edit-button>
</li>
<li v-if="props.storedObject.type != 'application/pdf' && is_extension_viewable(props.storedObject.type) && props.canConvertPdf && props.storedObject.status !== 'stored_object_created'">
<li v-if="isConvertibleToPdf">
<convert-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></convert-button>
</li>
<li v-if="props.canDownload">
<download-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></download-button>
<li v-if="isDownloadable">
<download-button :stored-object="props.storedObject" :at-version="props.storedObject.currentVersion" :filename="filename" :classes="{'dropdown-item': true}"></download-button>
</li>
</ul>
</div>
@@ -29,20 +29,20 @@
<script lang="ts" setup>
import {onMounted} from "vue";
import {computed, onMounted} from "vue";
import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
import {is_extension_editable, is_extension_viewable, is_object_ready} from "./StoredObjectButton/helpers";
import {
StoredObject, StoredObjectCreated,
StoredObjectStatusChange,
StoredObject,
StoredObjectStatusChange, StoredObjectVersion,
WopiEditButtonExecutableBeforeLeaveFunction
} from "../types";
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
interface DocumentActionButtonsGroupConfig {
storedObject: StoredObject|StoredObjectCreated,
storedObject: StoredObject,
small?: boolean,
canEdit?: boolean,
canDownload?: boolean,
@@ -95,11 +95,44 @@ let tryiesForReady = 0;
*/
const maxTryiesForReady = 120;
const isButtonGroupDisplayable = computed<boolean>(() => {
return isDownloadable.value || isEditableOnline.value || isEditableOnDesktop.value || isConvertibleToPdf.value;
});
const isDownloadable = computed<boolean>(() => {
return props.storedObject.status === 'ready'
// happens when the stored object version is just added, but not persisted
|| (props.storedObject.currentVersion !== null && props.storedObject.status === 'empty')
});
const isEditableOnline = computed<boolean>(() => {
return props.storedObject.status === 'ready'
&& props.storedObject._permissions.canEdit
&& props.canEdit
&& props.storedObject.currentVersion !== null
&& is_extension_editable(props.storedObject.currentVersion.type)
&& props.storedObject.currentVersion.persisted !== false;
});
const isEditableOnDesktop = computed<boolean>(() => {
return isEditableOnline.value;
});
const isConvertibleToPdf = computed<boolean>(() => {
return props.storedObject.status === 'ready'
&& props.storedObject._permissions.canSee
&& props.canConvertPdf
&& props.storedObject.currentVersion !== null
&& is_extension_viewable(props.storedObject.currentVersion.type)
&& props.storedObject.currentVersion.type !== 'application/pdf'
&& props.storedObject.currentVersion.persisted !== false;
})
const checkForReady = function(): void {
if (
'ready' === props.storedObject.status
|| 'empty' === props.storedObject.status
|| 'failure' === props.storedObject.status
|| 'stored_object_created' === props.storedObject.status
// stop reloading if the page stays opened for a long time
|| tryiesForReady > maxTryiesForReady
) {

View File

@@ -136,7 +136,6 @@ console.log(PdfWorker); // incredible but this is needed
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import {
build_download_info_link,
download_and_decrypt_doc,
} from "../StoredObjectButton/helpers";
@@ -160,7 +159,6 @@ declare global {
const $toast = useToast();
const signature = window.signature;
const urlInfo = build_download_info_link(signature.storedObject.filename);
const mountPdf = async (url: string) => {
const loadingTask = pdfjsLib.getDocument(url);
@@ -192,11 +190,7 @@ const setPage = async (page: number) => {
async function downloadAndOpen(): Promise<Blob> {
let raw;
try {
raw = await download_and_decrypt_doc(
urlInfo,
signature.storedObject.keyInfos,
new Uint8Array(signature.storedObject.iv)
);
raw = await download_and_decrypt_doc(signature.storedObject, signature.storedObject.currentVersion);
} catch (e) {
console.error("error while downloading and decrypting document", e);
throw e;

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import {StoredObject, StoredObjectCreated} from "../../types";
import {encryptFile, uploadFile} from "../_components/helper";
import {StoredObject, StoredObjectVersionCreated} from "../../types";
import {encryptFile, fetchNewStoredObject, uploadVersion} from "../../js/async-upload/uploader";
import {computed, ref, Ref} from "vue";
interface DropFileConfig {
existingDoc?: StoredObjectCreated|StoredObject,
existingDoc?: StoredObject,
}
const props = defineProps<DropFileConfig>();
const props = withDefaults(defineProps<DropFileConfig>(), {existingDoc: null});
const emit = defineEmits<{
(e: 'addDocument', stored_object: StoredObjectCreated): void,
(e: 'addDocument', {stored_object_version: StoredObjectVersionCreated, stored_object: StoredObject}): void,
}>();
const is_dragging: Ref<boolean> = ref(false);
@@ -34,7 +34,6 @@ const onDragLeave = (e: Event) => {
}
const onDrop = (e: DragEvent) => {
console.log('on drop', e);
e.preventDefault();
const files = e.dataTransfer?.files;
@@ -64,7 +63,6 @@ const onZoneClick = (e: Event) => {
const onFileChange = async (event: Event): Promise<void> => {
const input = event.target as HTMLInputElement;
console.log('event triggered', input);
if (input.files && input.files[0]) {
console.log('file added', input.files[0]);
@@ -80,21 +78,28 @@ const onFileChange = async (event: Event): Promise<void> => {
const handleFile = async (file: File): Promise<void> => {
uploading.value = true;
const type = file.type;
const buffer = await file.arrayBuffer();
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
const filename = await uploadFile(encrypted);
console.log(iv, jsonWebKey);
const storedObject: StoredObjectCreated = {
filename: filename,
iv,
keyInfos: jsonWebKey,
type: type,
status: "stored_object_created",
// create a stored_object if not exists
let stored_object;
if (null === props.existingDoc) {
stored_object = await fetchNewStoredObject();
} else {
stored_object = props.existingDoc;
}
emit('addDocument', storedObject);
const buffer = await file.arrayBuffer();
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
const filename = await uploadVersion(encrypted, stored_object);
const stored_object_version: StoredObjectVersionCreated = {
filename: filename,
iv: Array.from(iv),
keyInfos: jsonWebKey,
type: type,
persisted: false,
}
emit('addDocument', {stored_object, stored_object_version});
uploading.value = false;
}
@@ -138,6 +143,11 @@ const handleFile = async (file: File): Promise<void> => {
flex-direction: column;
justify-content: center;
align-items: center;
p {
// require for display in DropFileModal
text-align: center;
}
}
& > .area {
@@ -148,8 +158,4 @@ const handleFile = async (file: File): Promise<void> => {
}
}
}
div.chill-collection ul.list-entry li.entry:nth-child(2n) {
}
</style>

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import {StoredObject, StoredObjectVersion} from "../../types";
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
import {computed, reactive} from "vue";
import {useToast} from 'vue-toast-notification';
interface DropFileConfig {
allowRemove: boolean,
existingDoc?: StoredObject,
}
const props = withDefaults(defineProps<DropFileConfig>(), {
allowRemove: false,
});
const emit = defineEmits<{
(e: 'addDocument', {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void,
(e: 'removeDocument'): void
}>();
const $toast = useToast();
const state = reactive({showModal: false});
const modalClasses = {"modal-dialog-centered": true, "modal-md": true};
const buttonState = computed<'add'|'replace'>(() => {
if (props.existingDoc === undefined || props.existingDoc === null) {
return 'add';
}
return 'replace';
})
function onAddDocument({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void {
const message = buttonState.value === 'add' ? "Document ajouté" : "Document remplacé";
$toast.success(message);
emit('addDocument', {stored_object_version, stored_object});
state.showModal = false;
}
function onRemoveDocument(): void {
emit('removeDocument');
}
function openModal(): void {
state.showModal = true;
}
function closeModal(): void {
state.showModal = false;
}
</script>
<template>
<button v-if="buttonState === 'add'" @click="openModal" class="btn btn-create">Ajouter un document</button>
<button v-else @click="openModal" class="btn btn-edit">Remplacer le document</button>
<modal v-if="state.showModal" :modal-dialog-class="modalClasses" @close="closeModal">
<template v-slot:body>
<drop-file-widget :existing-doc="existingDoc" :allow-remove="allowRemove" @add-document="onAddDocument" @remove-document="onRemoveDocument" ></drop-file-widget>
</template>
</modal>
</template>
<style scoped lang="scss">
</style>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import {StoredObject, StoredObjectCreated} from "../../types";
import {StoredObject, StoredObjectVersion} from "../../types";
import {computed, ref, Ref} from "vue";
import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface DropFileConfig {
allowRemove: boolean,
existingDoc?: StoredObjectCreated|StoredObject,
existingDoc?: StoredObject,
}
const props = withDefaults(defineProps<DropFileConfig>(), {
@@ -15,8 +15,8 @@ const props = withDefaults(defineProps<DropFileConfig>(), {
});
const emit = defineEmits<{
(e: 'addDocument', stored_object: StoredObjectCreated): void,
(e: 'removeDocument', stored_object: null): void
(e: 'addDocument', {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void,
(e: 'removeDocument'): void
}>();
const has_existing_doc = computed<boolean>(() => {
@@ -45,14 +45,14 @@ const dav_link_href = computed<string|undefined>(() => {
return props.existingDoc._links?.dav_link?.href;
})
const onAddDocument = (s: StoredObjectCreated): void => {
emit('addDocument', s);
const onAddDocument = ({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void => {
emit('addDocument', {stored_object, stored_object_version});
}
const onRemoveDocument = (e: Event): void => {
e.stopPropagation();
e.preventDefault();
emit('removeDocument', null);
emit('removeDocument');
}
</script>

View File

@@ -10,7 +10,7 @@
import {build_convert_link, download_and_decrypt_doc, download_doc} from "./helpers";
import mime from "mime";
import {reactive} from "vue";
import {StoredObject, StoredObjectCreated} from "../../types";
import {StoredObject} from "../../types";
interface ConvertButtonConfig {
storedObject: StoredObject,
@@ -45,7 +45,7 @@ async function download_and_open(event: Event): Promise<void> {
</script>
<style scoped lang="sass">
<style scoped lang="scss">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}

View File

@@ -63,4 +63,7 @@ const editionUntilFormatted = computed<string>(() => {
.desktop-edit {
text-align: center;
}
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@@ -11,12 +11,13 @@
<script lang="ts" setup>
import {reactive, ref, nextTick, onMounted} from "vue";
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
import {download_and_decrypt_doc} from "./helpers";
import mime from "mime";
import {StoredObject, StoredObjectCreated} from "../../types";
import {StoredObject, StoredObjectVersion} from "../../types";
interface DownloadButtonConfig {
storedObject: StoredObject|StoredObjectCreated,
storedObject: StoredObject,
atVersion: StoredObjectVersion,
classes: { [k: string]: boolean },
filename?: string,
}
@@ -33,8 +34,9 @@ const state: DownloadButtonState = reactive({is_ready: false, is_running: false,
const open_button = ref<HTMLAnchorElement | null>(null);
function buildDocumentName(): string {
const document_name = props.filename || 'document';
const ext = mime.getExtension(props.storedObject.type);
const document_name = props.filename ?? props.storedObject.title ?? 'document';
const ext = mime.getExtension(props.atVersion.type);
if (null !== ext) {
return document_name + '.' + ext;
@@ -58,38 +60,26 @@ async function download_and_open(event: Event): Promise<void> {
return;
}
const urlInfo = build_download_info_link(props.storedObject.filename);
let raw;
try {
raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion);
} catch (e) {
console.error("error while downloading and decrypting document");
console.error(e);
throw e;
}
console.log('document downloading (and decrypting) successfully');
console.log('creating the url')
state.href_url = window.URL.createObjectURL(raw);
console.log('url created', state.href_url);
state.is_running = false;
state.is_ready = true;
console.log('new button marked as ready');
console.log('will click on button');
console.log('openbutton is now', open_button.value);
await nextTick();
console.log('next tick actions');
console.log('openbutton after next tick', open_button.value);
open_button.value?.click();
console.log('open button should have been clicked');
}
</script>
<style scoped lang="sass">
<style scoped lang="scss">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}

View File

@@ -8,7 +8,7 @@
<script lang="ts" setup>
import WopiEditButton from "./WopiEditButton.vue";
import {build_wopi_editor_link} from "./helpers";
import {StoredObject, StoredObjectCreated, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
interface WopiEditButtonConfig {
storedObject: StoredObject,
@@ -22,7 +22,6 @@ const props = defineProps<WopiEditButtonConfig>();
let executed = false;
async function beforeLeave(event: Event): Promise<true> {
console.log(executed);
if (props.executeBeforeLeave === undefined || executed === true) {
return Promise.resolve(true);
}
@@ -39,7 +38,7 @@ async function beforeLeave(event: Event): Promise<true> {
}
</script>
<style scoped lang="sass">
<style scoped lang="scss">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}

View File

@@ -1,4 +1,5 @@
import {StoredObject, StoredObjectStatus, StoredObjectStatusChange} from "../../types";
import {StoredObject, StoredObjectStatus, StoredObjectStatusChange, StoredObjectVersion} from "../../types";
import {makeFetch} from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
const MIMES_EDIT = new Set([
'application/vnd.ms-powerpoint',
@@ -97,6 +98,13 @@ const MIMES_VIEW = new Set([
]
])
export interface SignedUrlGet {
method: 'GET'|'HEAD',
url: string,
expires: number,
object_name: string,
}
function is_extension_editable(mimeType: string): boolean {
return MIMES_EDIT.has(mimeType);
}
@@ -109,8 +117,20 @@ function build_convert_link(uuid: string) {
return `/chill/wopi/convert/${uuid}`;
}
function build_download_info_link(object_name: string) {
return `/asyncupload/temp_url/generate/GET?object_name=${object_name}`;
function build_download_info_link(storedObject: StoredObject, atVersion: null|StoredObjectVersion): string {
const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
if (null !== atVersion) {
const params = new URLSearchParams({version: atVersion.filename});
return url + '?' + params.toString();
}
return url;
}
async function download_info_link(storedObject: StoredObject, atVersion: null|StoredObjectVersion): Promise<SignedUrlGet> {
return makeFetch('GET', build_download_info_link(storedObject, atVersion));
}
function build_wopi_editor_link(uuid: string, returnPath?: string) {
@@ -131,43 +151,39 @@ function download_doc(url: string): Promise<Blob> {
});
}
async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKey, iv: Uint8Array): Promise<Blob>
async function download_and_decrypt_doc(storedObject: StoredObject, atVersion: null|StoredObjectVersion): Promise<Blob>
{
const algo = 'AES-CBC';
// get an url to download the object
const downloadInfoResponse = await window.fetch(urlGenerator);
if (!downloadInfoResponse.ok) {
throw new Error("error while downloading url " + downloadInfoResponse.status + " " + downloadInfoResponse.statusText);
const atVersionToDownload = atVersion ?? storedObject.currentVersion;
if (null === atVersionToDownload) {
throw new Error("no version associated to stored object");
}
const downloadInfo = await downloadInfoResponse.json() as {url: string};
const downloadInfo= await download_info_link(storedObject, atVersionToDownload);
const rawResponse = await window.fetch(downloadInfo.url);
if (!rawResponse.ok) {
throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText);
}
if (iv.length === 0) {
console.log('returning document immediatly');
if (atVersionToDownload.iv.length === 0) {
return rawResponse.blob();
}
console.log('start decrypting doc');
const rawBuffer = await rawResponse.arrayBuffer();
try {
const key = await window.crypto.subtle
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
console.log('key created');
.importKey('jwk', atVersionToDownload.keyInfos, { name: algo }, false, ['decrypt']);
const iv = Uint8Array.from(atVersionToDownload.iv);
const decrypted = await window.crypto.subtle
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
console.log('doc decrypted');
return Promise.resolve(new Blob([decrypted]));
} catch (e) {
console.error('get error while keys and decrypt operations');
console.error('encounter error while keys and decrypt operations');
console.error(e);
throw e;
@@ -188,7 +204,6 @@ async function is_object_ready(storedObject: StoredObject): Promise<StoredObject
export {
build_convert_link,
build_download_info_link,
build_wopi_editor_link,
download_and_decrypt_doc,
download_doc,

View File

@@ -1,174 +0,0 @@
<template>
<a :class="btnClasses" :title="$t(buttonTitle)" @click="openModal">
<span>{{ $t(buttonTitle) }}</span>
</a>
<teleport to="body">
<div>
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<template v-slot:header>
{{ $t('upload_a_document') }}
</template>
<template v-slot:body>
<div id="dropZoneWrapper" ref="dropZoneWrapper">
<div
data-stored-object="data-stored-object"
:data-label-preparing="$t('data_label_preparing')"
:data-label-quiet-button="$t('data_label_quiet_button')"
:data-label-ready="$t('data_label_ready')"
:data-dict-file-too-big="$t('data_dict_file_too_big')"
:data-dict-default-message="$t('data_dict_default_message')"
:data-dict-remove-file="$t('data_dict_remove_file')"
:data-dict-max-files-exceeded="$t('data_dict_max_files_exceeded')"
:data-dict-cancel-upload="$t('data_dict_cancel_upload')"
:data-dict-cancel-upload-confirm="$t('data_dict_cancel_upload_confirm')"
:data-dict-upload-canceled="$t('data_dict_upload_canceled')"
:data-dict-remove="$t('data_dict_remove')"
:data-allow-remove="!options.required"
data-temp-url-generator="/asyncupload/temp_url/generate/GET">
<input
type="hidden"
data-async-file-upload="data-async-file-upload"
data-generate-temp-url-post="/asyncupload/temp_url/generate/post?expires_delay=180&amp;submit_delay=3600"
data-temp-url-get="/asyncupload/temp_url/generate/GET"
:data-max-files="options.maxFiles"
:data-max-post-size="options.maxPostSize"
:v-model="dataAsyncFileUpload"
>
<input
type="hidden"
data-stored-object-key="1"
>
<input
type="hidden"
data-stored-object-iv="1"
>
<input
type="hidden"
data-async-file-type="1"
>
</div>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-create"
@click.prevent="saveDocument">
{{ $t('action.add')}}
</button>
</template>
</modal>
</div>
</teleport>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import { searchForZones } from '../../module/async_upload/uploader';
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
const i18n = {
messages: {
fr: {
upload_a_document: "Téléversez un document",
data_label_preparing: "Chargement...",
data_label_quiet_button: "Téléchargez le fichier existant",
data_label_ready: "Prêt à montrer",
data_dict_file_too_big: "Fichier trop volumineux",
data_dict_default_message: "Glissez votre fichier ou cliquez ici",
data_dict_remove_file: "Enlevez votre fichier pour en téléversez un autre",
data_dict_max_files_exceeded: "Nombre maximum de fichiers atteint. Enlevez les fichiers précédents",
data_dict_cancel_upload: "Annulez le téléversement",
data_dict_cancel_upload_confirm: "Êtes-vous sûr·e de vouloir annuler ce téléversement?",
data_dict_upload_canceled: "Téléversement annulé",
data_dict_remove: "Enlevez le fichier existant",
}
}
};
export default {
name: "AddAsyncUpload",
components: {
Modal
},
i18n,
props: {
buttonTitle: {
type: String,
default: 'Ajouter un document',
},
options: {
type: Object,
default: {
maxFiles: 1,
maxPostSize: 262144000, // 250MB
required: false,
}
},
btnClasses: {
type: Object,
default: {
btn: true,
'btn-create': true
}
}
},
emits: ['addDocument'],
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-dialog-centered modal-md"
},
}
},
updated() {
if (this.modal.showModal){
searchForZones(this.$refs.dropZoneWrapper);
}
},
methods: {
openModal() {
this.modal.showModal = true;
},
saveDocument() {
const dropzone = this.$refs.dropZoneWrapper;
if (dropzone) {
const inputKey = dropzone.querySelector('input[data-stored-object-key]');
const inputIv = dropzone.querySelector('input[data-stored-object-iv]');
const inputObject = dropzone.querySelector('input[data-async-file-upload]');
const inputType = dropzone.querySelector('input[data-async-file-type]');
const url = '/api/1.0/docstore/stored-object.json';
const body = {
filename: inputObject.value,
keyInfos: JSON.parse(inputKey.value),
iv: JSON.parse(inputIv.value),
type: inputType.value,
};
makeFetch('POST', url, body)
.then(r => {
this.$emit("addDocument", r);
this.modal.showModal = false;
})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
console.error(error);
this.$toast.open({message: 'An error occurred'});
}
});
} else {
this.$toast.open({message: 'An error occurred - drop zone not found'});
}
}
}
}
</script>

View File

@@ -1,45 +0,0 @@
<template>
<a
class="btn btn-download"
:title="$t(buttonTitle)"
:data-key=JSON.stringify(storedObject.keyInfos)
:data-iv=JSON.stringify(storedObject.iv)
:data-mime-type=storedObject.type
:data-label-preparing="$t('dataLabelPreparing')"
:data-label-ready="$t('dataLabelReady')"
:data-temp-url-get-generator="url"
@click.once="downloadDocument">
</a>
</template>
<script>
import { download } from '../../module/async_upload/downloader';
const i18n = {
messages: {
fr: {
dataLabelPreparing: "Chargement...",
dataLabelReady: "",
}
}
};
export default {
name: "AddAsyncUploadDownloader",
i18n,
props: [
'buttonTitle',
'storedObject'
],
computed: {
url() {
return `/asyncupload/temp_url/generate/GET?object_name=${this.storedObject.filename}`;
}
},
methods: {
downloadDocument(e) {
download(e.target);
}
}
}
</script>