From bb7b36b5bd7497e9292ce1b48cc43b0557e504db Mon Sep 17 00:00:00 2001
From: xiaocp2009 <39615122+xiaocp2009@users.noreply.github.com>
Date: Mon, 1 Sep 2025 11:16:18 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=80=81=E6=97=A7=E6=B5=8F?=
=?UTF-8?q?=E8=A7=88=E5=99=A8(=E7=81=AB=E7=8B=90)=E6=B5=81=E5=BC=8F?=
=?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cds-fontend-2025.V1/package.json | 1 +
cds-fontend-2025.V1/pnpm-lock.yaml | 9 +
cds-fontend-2025.V1/public/mitm.html | 166 ++++++++++++++++++
cds-fontend-2025.V1/public/sw.js | 130 ++++++++++++++
.../src/hooks/business/download.ts | 10 ++
5 files changed, 316 insertions(+)
create mode 100644 cds-fontend-2025.V1/public/mitm.html
create mode 100644 cds-fontend-2025.V1/public/sw.js
diff --git a/cds-fontend-2025.V1/package.json b/cds-fontend-2025.V1/package.json
index f9245f2..ac56832 100644
--- a/cds-fontend-2025.V1/package.json
+++ b/cds-fontend-2025.V1/package.json
@@ -52,6 +52,7 @@
"echarts": "5.6.0",
"highlight.js": "^11.11.1",
"jsencrypt": "^3.3.2",
+ "web-streams-polyfill": "^3.2.1",
"json5": "2.2.3",
"monaco-editor": "^0.52.2",
"naive-ui": "2.42.0",
diff --git a/cds-fontend-2025.V1/pnpm-lock.yaml b/cds-fontend-2025.V1/pnpm-lock.yaml
index d0ace8c..a939fb6 100644
--- a/cds-fontend-2025.V1/pnpm-lock.yaml
+++ b/cds-fontend-2025.V1/pnpm-lock.yaml
@@ -92,6 +92,9 @@ importers:
vue-router:
specifier: 4.5.1
version: 4.5.1(vue@3.5.17(typescript@5.8.3))
+ web-streams-polyfill:
+ specifier: ^3.2.1
+ version: 3.3.3
devDependencies:
'@elegant-router/vue':
specifier: 0.3.8
@@ -4098,6 +4101,10 @@ packages:
peerDependencies:
vue: ^3.0.11
+ web-streams-polyfill@3.3.3:
+ resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
+ engines: {node: '>= 8'}
+
webpack-sources@3.3.3:
resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
engines: {node: '>=10.13.0'}
@@ -8218,6 +8225,8 @@ snapshots:
vooks: 0.2.12(vue@3.5.17(typescript@5.8.3))
vue: 3.5.17(typescript@5.8.3)
+ web-streams-polyfill@3.3.3: {}
+
webpack-sources@3.3.3: {}
webpack-virtual-modules@0.6.2: {}
diff --git a/cds-fontend-2025.V1/public/mitm.html b/cds-fontend-2025.V1/public/mitm.html
new file mode 100644
index 0000000..04450a9
--- /dev/null
+++ b/cds-fontend-2025.V1/public/mitm.html
@@ -0,0 +1,166 @@
+
+
diff --git a/cds-fontend-2025.V1/public/sw.js b/cds-fontend-2025.V1/public/sw.js
new file mode 100644
index 0000000..dd75c1a
--- /dev/null
+++ b/cds-fontend-2025.V1/public/sw.js
@@ -0,0 +1,130 @@
+/* global self ReadableStream Response */
+
+self.addEventListener('install', () => {
+ self.skipWaiting()
+})
+
+self.addEventListener('activate', event => {
+ event.waitUntil(self.clients.claim())
+})
+
+const map = new Map()
+
+// This should be called once per download
+// Each event has a dataChannel that the data will be piped through
+self.onmessage = event => {
+ // We send a heartbeat every x second to keep the
+ // service worker alive if a transferable stream is not sent
+ if (event.data === 'ping') {
+ return
+ }
+
+ const data = event.data
+ const downloadUrl = data.url || self.registration.scope + Math.random() + '/' + (typeof data === 'string' ? data : data.filename)
+ const port = event.ports[0]
+ const metadata = new Array(3) // [stream, data, port]
+
+ metadata[1] = data
+ metadata[2] = port
+
+ // Note to self:
+ // old streamsaver v1.2.0 might still use `readableStream`...
+ // but v2.0.0 will always transfer the stream through MessageChannel #94
+ if (event.data.readableStream) {
+ metadata[0] = event.data.readableStream
+ } else if (event.data.transferringReadable) {
+ port.onmessage = evt => {
+ port.onmessage = null
+ metadata[0] = evt.data.readableStream
+ }
+ } else {
+ metadata[0] = createStream(port)
+ }
+
+ map.set(downloadUrl, metadata)
+ port.postMessage({ download: downloadUrl })
+}
+
+function createStream (port) {
+ // ReadableStream is only supported by chrome 52
+ return new ReadableStream({
+ start (controller) {
+ // When we receive data on the messageChannel, we write
+ port.onmessage = ({ data }) => {
+ if (data === 'end') {
+ return controller.close()
+ }
+
+ if (data === 'abort') {
+ controller.error('Aborted the download')
+ return
+ }
+
+ controller.enqueue(data)
+ }
+ },
+ cancel (reason) {
+ console.log('user aborted', reason)
+ port.postMessage({ abort: true })
+ }
+ })
+}
+
+self.onfetch = event => {
+ const url = event.request.url
+
+ // this only works for Firefox
+ if (url.endsWith('/ping')) {
+ return event.respondWith(new Response('pong'))
+ }
+
+ const hijacke = map.get(url)
+
+ if (!hijacke) return null
+
+ const [ stream, data, port ] = hijacke
+
+ map.delete(url)
+
+ // Not comfortable letting any user control all headers
+ // so we only copy over the length & disposition
+ const responseHeaders = new Headers({
+ 'Content-Type': 'application/octet-stream; charset=utf-8',
+
+ // To be on the safe side, The link can be opened in a iframe.
+ // but octet-stream should stop it.
+ 'Content-Security-Policy': "default-src 'none'",
+ 'X-Content-Security-Policy': "default-src 'none'",
+ 'X-WebKit-CSP': "default-src 'none'",
+ 'X-XSS-Protection': '1; mode=block',
+ 'Cross-Origin-Embedder-Policy': 'require-corp'
+ })
+
+ let headers = new Headers(data.headers || {})
+
+ if (headers.has('Content-Length')) {
+ responseHeaders.set('Content-Length', headers.get('Content-Length'))
+ }
+
+ if (headers.has('Content-Disposition')) {
+ responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'))
+ }
+
+ // data, data.filename and size should not be used anymore
+ if (data.size) {
+ console.warn('Depricated')
+ responseHeaders.set('Content-Length', data.size)
+ }
+
+ let fileName = typeof data === 'string' ? data : data.filename
+ if (fileName) {
+ console.warn('Depricated')
+ // Make filename RFC5987 compatible
+ fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A')
+ responseHeaders.set('Content-Disposition', "attachment; filename*=UTF-8''" + fileName)
+ }
+
+ event.respondWith(new Response(stream, { headers: responseHeaders }))
+
+ port.postMessage({ debug: 'Download started' })
+}
diff --git a/cds-fontend-2025.V1/src/hooks/business/download.ts b/cds-fontend-2025.V1/src/hooks/business/download.ts
index 210e125..a859c23 100644
--- a/cds-fontend-2025.V1/src/hooks/business/download.ts
+++ b/cds-fontend-2025.V1/src/hooks/business/download.ts
@@ -3,6 +3,7 @@ import { errorCodeRecord } from '@/constants/common';
import { localStg } from '@/utils/storage';
import { getServiceBaseURL } from '@/utils/service';
import { transformToURLSearchParams } from '@/utils/common';
+import { WritableStream } from 'web-streams-polyfill/ponyfill';
interface RequestConfig {
method: 'GET' | 'POST';
@@ -51,6 +52,15 @@ export function useDownload() {
contentLength?: number
): Promise {
window.$loading?.endLoading();
+
+ // 检查浏览器是否原生支持 WritableStream,若不支持则使用polyfill
+ if (typeof window.WritableStream === 'undefined') {
+ window.WritableStream = WritableStream;
+ // 确保StreamSaver也使用相同的Polyfill
+ StreamSaver.WritableStream = WritableStream;
+ }
+
+ StreamSaver.mitm = '/mitm.html' // 增加此行代码
const fileStream = StreamSaver.createWriteStream(filename, { size: contentLength });
if (window.WritableStream && readableStream?.pipeTo) {