From 1e64835f076d1527b868ef710b657cc448329638 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Sun, 8 Jan 2023 00:04:03 +0900 Subject: [PATCH 01/34] bump: 0.15.1-SNAPSHOT --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f898f3569..dc95ce89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "piping-ui", - "version": "0.15.0", + "version": "0.15.1-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "piping-ui", - "version": "0.15.0", + "version": "0.15.1-SNAPSHOT", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index baf787770..d65d40a16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "piping-ui", - "version": "0.15.0", + "version": "0.15.1-SNAPSHOT", "private": true, "author": "Ryo Ota (https://github.com/nwtgck)", "scripts": { From 711d6de96a38c51e85df6d82c5719d569dbcee5c Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Sun, 8 Jan 2023 14:01:48 +0900 Subject: [PATCH 02/34] clear input files and text after sent --- src/components/DataUploader.vue | 8 +++++++- src/components/FilePondWrapper.vue | 8 ++++++++ src/components/PipingUI.vue | 13 +++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/components/DataUploader.vue b/src/components/DataUploader.vue index 3d422f63b..5af323f6f 100644 --- a/src/components/DataUploader.vue +++ b/src/components/DataUploader.vue @@ -122,12 +122,13 @@ export type DataUploaderProps = { serverUrl: string, secretPath: string, protection: Protection, + onSentSuccessfully: () => void, }; From 30cf16c7eae5858c02c3b42858a306655303feb2 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Tue, 10 Jan 2023 22:35:17 +0900 Subject: [PATCH 19/34] not occur an error when verified extension is no compatible because the purpose of the extension provides better user experience --- src/piping-ui-auth/index.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/piping-ui-auth/index.ts b/src/piping-ui-auth/index.ts index afb9c4c3d..f5ed1bbdb 100644 --- a/src/piping-ui-auth/index.ts +++ b/src/piping-ui-auth/index.ts @@ -351,12 +351,14 @@ export async function keyExchangeAndReceiveVerified(serverUrl: string, secretPat }; } const verifiedExtensionParseReturn = verifiedExtensionType.safeParse(extension); - if (!verifiedExtensionParseReturn.success) { - return { - type: "error", - error: { code: 'key_exchange_error', keyExchangeError: { code: 'invalid_parcel_format' }}, - }; - } + const verifiedExtension = (() => { + if (verifiedExtensionParseReturn.success) { + return verifiedExtensionParseReturn.data; + } + // Should not occur an error aggressively because the purpose of the extension provides better user experience. + console.log("extension is not compatible", extension); + return undefined; + })(); return { type: 'key', key, @@ -364,10 +366,10 @@ export async function keyExchangeAndReceiveVerified(serverUrl: string, secretPat mainPath, verificationCode, dataMeta: { - mimeType: verifiedExtensionParseReturn.data.data_meta.mime_type, - size: verifiedExtensionParseReturn.data.data_meta.size, - fileName: verifiedExtensionParseReturn.data.data_meta.file_name, - fileExtension: verifiedExtensionParseReturn.data.data_meta.file_extension, + mimeType: verifiedExtension?.data_meta.mime_type, + size: verifiedExtension?.data_meta.size, + fileName: verifiedExtension?.data_meta.file_name, + fileExtension: verifiedExtension?.data_meta.file_extension, }, }; } From 097d5aa5087654d7dd034320367187ce681f914a Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Tue, 10 Jan 2023 22:46:36 +0900 Subject: [PATCH 20/34] update log message when Service Worker is ready --- src/registerServiceWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registerServiceWorker.ts b/src/registerServiceWorker.ts index 24d42a4bb..a3519aab5 100644 --- a/src/registerServiceWorker.ts +++ b/src/registerServiceWorker.ts @@ -3,7 +3,7 @@ const swDownloadAsync = () => import("@/sw-download"); register(`${process.env.BASE_URL}service-worker.js`, { ready () { - console.log('App is being served from cache by a service worker.'); + console.log('Service worker is ready.'); }, async registered () { console.log('Service worker has been registered.'); From 4d3cd9bd509d72880b56d94d4bdff5ae806e0bb4 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Tue, 10 Jan 2023 22:47:06 +0900 Subject: [PATCH 21/34] not dispatch unused event 'swUpdated' --- src/registerServiceWorker.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/registerServiceWorker.ts b/src/registerServiceWorker.ts index a3519aab5..afe4d8d59 100644 --- a/src/registerServiceWorker.ts +++ b/src/registerServiceWorker.ts @@ -18,9 +18,6 @@ register(`${process.env.BASE_URL}service-worker.js`, { }, updated (registration) { console.log('New content is available; please refresh.') - document.dispatchEvent( - new CustomEvent('swUpdated', { detail: registration }) - ); }, offline () { console.log('No internet connection found. App is running in offline mode.') From a19c04922d5257991dde3280502829af2e45f0fd Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 11 Jan 2023 09:58:39 +0900 Subject: [PATCH 22/34] set total bytes for better progress bar experience in passwordless protection --- src/components/DataViewer.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/DataViewer.vue b/src/components/DataViewer.vue index 44f5ca304..5c550c3a7 100644 --- a/src/components/DataViewer.vue +++ b/src/components/DataViewer.vue @@ -359,6 +359,9 @@ onMounted(async () => { rawStream = pipingUiRobust.receiveReadableStream(props.composedProps.serverUrl, keyExchangeRes.mainPath, { abortSignal: abortController.signal, }); + if (keyExchangeRes.dataMeta.size !== undefined) { + progressSetting.value.totalBytes = keyExchangeRes.dataMeta.size; + } } else { const abortController = new AbortController(); canceledPromise.then(() => { @@ -383,8 +386,8 @@ onMounted(async () => { return; } const contentLengthStr = res.headers.get("Content-Length"); - // NOTE: Content-Length is encrypted byte size if password is defined - if (contentLengthStr !== null && password.value === undefined) { + // NOTE: Should not use Content-Length when password is defined because it is encrypted byte size + if (contentLengthStr !== null && keyExchangeRes.protectionType === "password") { progressSetting.value.totalBytes = parseInt(contentLengthStr, 10); } rawStream = res.body!; From ba72de9c1a4f72fdad27888c149e1f030f3ba64a Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 11 Jan 2023 20:02:57 +0900 Subject: [PATCH 23/34] chore: add crbug link --- src/components/DataDownloader.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/DataDownloader.vue b/src/components/DataDownloader.vue index 5ccb8ef4e..674b99530 100644 --- a/src/components/DataDownloader.vue +++ b/src/components/DataDownloader.vue @@ -308,6 +308,7 @@ onMounted(async () => { // NOTE: The feature detection can not be created because it would confirm the downloaded file. if (isFirefox()) { // NOTE: With "download" attributes, Chrome 108 and Safari 16.1 bypass Service Worker + // crbug: https://bugs.chromium.org/p/chromium/issues/detail?id=468227 // NOTE: Without "download" attribute, Firefox frequently fails to download a large file. Passwordless protection is stable without "download" attribute because it uses Piping UI Robust, which transfer small chunks especially first chunk even in Firefox. // For testing in Firefox, you can enable "Block pop-up windows" in your preference. a.download = fileName; From bf46ebee70f98100ba383724e560bb72895d210e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 20:04:57 +0900 Subject: [PATCH 24/34] build(deps): bump vuetify from 2.6.13 to 2.6.14 (#1411) Bumps [vuetify](https://github.com/vuetifyjs/vuetify/tree/HEAD/packages/vuetify) from 2.6.13 to 2.6.14. - [Release notes](https://github.com/vuetifyjs/vuetify/releases) - [Commits](https://github.com/vuetifyjs/vuetify/commits/v2.6.14/packages/vuetify) --- updated-dependencies: - dependency-name: vuetify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 833f48af0..356bf4ac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "url-join": "^4.0.1", "vue": "^2.7.10", "vue-filepond": "^6.0.3", - "vuetify": "^2.6.10", + "vuetify": "^2.6.14", "zod": "^3.20.2" }, "devDependencies": { @@ -15268,9 +15268,9 @@ "dev": true }, "node_modules/vuetify": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.13.tgz", - "integrity": "sha512-HhJi52IzhfrmWFwcYFUiA1GRIzz9smbR06Lj61Ml5HgD5PBcMiDywUnNPVid1VsXO4qWpBU6kO3O89uTxH1yzw==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.14.tgz", + "integrity": "sha512-nr6wU3uTzhhEPssH23cW0Ee/hCrayp7cjl3nNjM2OmNwiJlV91tZiL1VO3597SqZyjh1xIa+m9J2rpKTSdIlrA==", "funding": { "type": "github", "url": "https://github.com/sponsors/johnleider" @@ -27821,9 +27821,9 @@ "dev": true }, "vuetify": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.13.tgz", - "integrity": "sha512-HhJi52IzhfrmWFwcYFUiA1GRIzz9smbR06Lj61Ml5HgD5PBcMiDywUnNPVid1VsXO4qWpBU6kO3O89uTxH1yzw==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.14.tgz", + "integrity": "sha512-nr6wU3uTzhhEPssH23cW0Ee/hCrayp7cjl3nNjM2OmNwiJlV91tZiL1VO3597SqZyjh1xIa+m9J2rpKTSdIlrA==", "requires": {} }, "vuetify-loader": { diff --git a/package.json b/package.json index a48902fba..9be60aae3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "url-join": "^4.0.1", "vue": "^2.7.10", "vue-filepond": "^6.0.3", - "vuetify": "^2.6.10", + "vuetify": "^2.6.14", "zod": "^3.20.2" }, "devDependencies": { From 813ad9e70a95146d9b02ad6935d07877e6b392b4 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 11 Jan 2023 21:15:24 +0900 Subject: [PATCH 25/34] show progress bar in DataDownload.vue when Service Worker is available --- src/components/DataDownloader.vue | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/components/DataDownloader.vue b/src/components/DataDownloader.vue index 674b99530..2498c4bca 100644 --- a/src/components/DataDownloader.vue +++ b/src/components/DataDownloader.vue @@ -13,6 +13,24 @@ + + + + + + {{ progressSetting.loadedBytes }}{{ !progressSetting.totalBytes ? "" : ` of ${progressSetting.totalBytes}` }} + + + + + + @@ -86,6 +104,8 @@ import {strings} from "@/strings/strings"; import {ecdsaP384SigningKeyPairPromise} from "@/states/ecdsaP384SigningKeyPairPromise"; import {firstAtLeastBlobFromReadableStream} from "@/utils/firstAtLeastBlobFromReadableStream"; import {decideFileName} from "@/piping-ui-utils/decideFileName"; +import {readableBytesString} from "../utils/readableBytesString"; +import {getReadableStreamWithProgress} from "@/utils/getReadableStreamWithProgress"; const FileSaverAsync = () => import('file-saver').then(p => p.default); const swDownloadAsync = () => import("@/sw-download"); @@ -98,6 +118,7 @@ const {promise: canceledPromise, resolve: cancel} = makePromise(); canceledPromise.then(() => { // canceled.value = true; }); +const canceled = ref(false); const {errorMessage, updateErrorMessage} = useErrorMessage(); const verificationStep = ref({type: 'initial'}); @@ -126,6 +147,23 @@ const rootElement = ref(); const pipingUiAuthVerificationCode = ref(); const openRetryDownload = ref(false); const retry_download_button = ref(); +const showsProgressBar = ref(false); +const progressSetting = ref<{loadedBytes: number, totalBytes?: number}>({ + loadedBytes: 0, + totalBytes: undefined, +}); +const progressPercentage = computed(() => { + // if (isDoneDownload.value) { + // return 100; + // } + if (progressSetting.value.totalBytes === undefined) { + return null; + } + if (progressSetting.value.totalBytes === 0) { + return 100; + } + return progressSetting.value.loadedBytes / progressSetting.value.totalBytes * 100; +}); // NOTE: Automatically download when mounted onMounted(async () => { @@ -231,6 +269,8 @@ onMounted(async () => { console.log("downloading streaming with the Service Worker and decrypting if need..."); const openPgpUtils = await openPgpUtilsAsync(); + showsProgressBar.value = true; + let readableStream: ReadableStream; let contentLengthStr: string | undefined = undefined; // Passwordless transfer always uses Piping UI Robust @@ -245,6 +285,9 @@ onMounted(async () => { keyExchangeRes.mainPath, { abortSignal: abortController.signal }, ); + if (keyExchangeRes.dataMeta.size !== undefined) { + progressSetting.value.totalBytes = keyExchangeRes.dataMeta.size; + } } else { const res = await fetch(downloadUrl.value); if (res.status !== 200) { @@ -252,7 +295,11 @@ onMounted(async () => { updateErrorMessage(() => strings.value?.['fetch_status_error']({status: res.status, message})); return; } + // NOTE: Should not use Content-Length when password is defined because it is encrypted byte size contentLengthStr = key === undefined ? res.headers.get("Content-Length") ?? undefined : undefined; + if (contentLengthStr !== undefined) { + progressSetting.value.totalBytes = parseInt(contentLengthStr, 10); + } readableStream = res.body! } @@ -290,8 +337,14 @@ onMounted(async () => { ...( mimeType === undefined ? [] : [ [ "Content-Type", mimeType ] ] satisfies [[string, string]] ), ['Content-Disposition', "attachment; filename*=UTF-8''" + escapedFileName], ]; + const {stream: readableStreamForDownloadWithProgress, cancel: cancelReadableStreamForDownloadWithProgress} = getReadableStreamWithProgress(readableStreamForDownload, (n) => { + progressSetting.value.loadedBytes += n; + }); + canceledPromise.then(() => { + cancelReadableStreamForDownloadWithProgress(); + }); // Enroll download ReadableStream and get sw-download ID - const {swDownloadId} = await enrollDownload(headers, readableStreamForDownload); + const {swDownloadId} = await enrollDownload(headers, readableStreamForDownloadWithProgress); // Download via Service Worker // NOTE: '/sw-download/v2' can be received by Service Worker in src/sw.js // NOTE: URL fragment is passed to Service Worker but not passed to Web server From 96937c99ddb2825b580b5e0dff64a09a4f81da11 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 11 Jan 2023 21:22:09 +0900 Subject: [PATCH 26/34] show percentage --- src/components/DataDownloader.vue | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/components/DataDownloader.vue b/src/components/DataDownloader.vue index 2498c4bca..e7c7e2a79 100644 --- a/src/components/DataDownloader.vue +++ b/src/components/DataDownloader.vue @@ -1,7 +1,14 @@