From 035d44b0882309a414b60b35be5965798343fe5c Mon Sep 17 00:00:00 2001 From: freeof123 Date: Tue, 4 Aug 2026 15:29:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=AF=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=A1=8C=E9=9D=A2/=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E4=B8=8A=E9=99=90=E3=80=82=E4=BF=AE=E6=94=B9?= =?UTF-8?q?locale=E7=A1=AE=E4=BF=9D=E6=B5=8F=E8=A7=88=E5=99=A8=E7=AB=AF?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.browser.mjs | 1 + package.json | 2 +- src/browser/downloadUnsealed.js | 13 ++- src/browser/stream_download.js | 7 +- src/common/locale.js | 4 +- test/downloadFunctions.spec.mjs | 28 ++++-- test/downloadUnsealedSizeLimit.spec.mjs | 112 ++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 test/downloadUnsealedSizeLimit.spec.mjs diff --git a/jest.config.browser.mjs b/jest.config.browser.mjs index 8a05d22..87e41c8 100644 --- a/jest.config.browser.mjs +++ b/jest.config.browser.mjs @@ -7,6 +7,7 @@ export default { "/test/downloadFunctions.spec.mjs", "/test/downloadCallbacks.spec.mjs", "/test/downloadUnsealedCallbacks.spec.mjs", + "/test/downloadUnsealedSizeLimit.spec.mjs", "/test/progress.spec.mjs", ], diff --git a/package.json b/package.json index af974fe..c04e4d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yeez-tech/meta-encryptor", - "version": "5.0.7", + "version": "5.1.0", "description": "Data Seal/Unseal for Fidelius", "main": "./build/commonjs/index.node.cjs", "module": "./build/es/index.browser.js", diff --git a/src/browser/downloadUnsealed.js b/src/browser/downloadUnsealed.js index 9352a89..b395c16 100644 --- a/src/browser/downloadUnsealed.js +++ b/src/browser/downloadUnsealed.js @@ -88,6 +88,8 @@ async function inspectSealed(url, log) { * @param {Function} [options.onDownloadReady] - HTTP 流首个数据块进入管道时触发(可关蒙层) * @param {Function} [options.onSuccess] * @param {Function} [options.onError] + * @param {number} [options.desktopLimit] - desktop plaintext size limit in bytes (default 1 GiB) + * @param {number} [options.mobileLimit] - mobile plaintext size limit in bytes (default 200 MiB) * @returns {Promise} */ export async function downloadUnsealed({ @@ -98,7 +100,11 @@ export async function downloadUnsealed({ onProgress, onDownloadReady, onSuccess, - onError + onError, + /** Override desktop plaintext size limit (bytes). Default 1 GiB. */ + desktopLimit, + /** Override mobile plaintext size limit (bytes). Default 200 MiB. */ + mobileLimit, }) { const log = makeLogger(onLog); const key = privateKey.trim() @@ -116,7 +122,10 @@ export async function downloadUnsealed({ log(`Plaintext size=${meta.plaintextSize} bytes, sealed=${meta.sealedContentSize} bytes, totalSize=${meta.totalSize}`); const mobile = isMobile() - const limit = mobile ? MOBILE_LIMIT : DESKTOP_LIMIT + const configured = mobile ? mobileLimit : desktopLimit + const platformDefault = mobile ? MOBILE_LIMIT : DESKTOP_LIMIT + const limit = + typeof configured === 'number' && configured > 0 ? configured : platformDefault const mode = mobile ? 'mobile' : 'desktop' const ua = typeof navigator !== 'undefined' ? navigator.userAgent : 'n/a'; log(`Detected ${mode} (ua=${ua}), limit ${(limit / 1024 / 1024).toFixed(0)} MB`); diff --git a/src/browser/stream_download.js b/src/browser/stream_download.js index 94eb67c..a5d7949 100644 --- a/src/browser/stream_download.js +++ b/src/browser/stream_download.js @@ -3,6 +3,8 @@ import { HttpSealedFileStream } from './HttpSealedFileStream.js' import { MetaEncryptorError } from '../common/errors.js'; import { createProgressTransformer, createDownloadReadyTransformer } from '../common/progress.js'; +const STREAMSAVER_LOAD_TIMEOUT_MS = 8000; + async function ensureStreamSaver(log) { if (typeof window === 'undefined') return null; if (window.streamSaver?.createWriteStream) return window.streamSaver; @@ -12,8 +14,9 @@ async function ensureStreamSaver(log) { await new Promise((resolve, reject) => { const s = document.createElement('script'); s.src = 'https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js'; - s.onload = resolve; - s.onerror = reject; + const timer = setTimeout(() => reject(new Error('StreamSaver load timeout')), STREAMSAVER_LOAD_TIMEOUT_MS); + s.onload = () => { clearTimeout(timer); resolve(); }; + s.onerror = (err) => { clearTimeout(timer); reject(err); }; document.head.appendChild(s); }); if (window.streamSaver?.createWriteStream) { diff --git a/src/common/locale.js b/src/common/locale.js index 0e6f960..5a1772b 100644 --- a/src/common/locale.js +++ b/src/common/locale.js @@ -1,5 +1,5 @@ -import en from '../locales/en.json'; -import zhCN from '../locales/zh-CN.json'; +import en from '../locales/en.json' with { type: 'json' }; +import zhCN from '../locales/zh-CN.json' with { type: 'json' }; const _bundled = { en, 'zh-CN': zhCN }; let _messages = null; diff --git a/test/downloadFunctions.spec.mjs b/test/downloadFunctions.spec.mjs index b935d21..dc797e6 100644 --- a/test/downloadFunctions.spec.mjs +++ b/test/downloadFunctions.spec.mjs @@ -62,7 +62,7 @@ describe('blobDownloadAndDecrypt', () => { ); expect(result).toEqual({ ok: true }); - expect(logs.some(m => m.includes('下载完成'))).toBe(true); + expect(logs.some(m => m.includes('Download complete'))).toBe(true); expect(blobContent).not.toBeNull(); } finally { URL.createObjectURL = origCOU; @@ -80,7 +80,7 @@ describe('blobDownloadAndDecrypt', () => { await expect( blobDownloadAndDecrypt('http://mock/file', 'a'.repeat(64), 'out.bin', { log: (m) => logs.push(m) }) ).rejects.toThrow('fetch fail'); - expect(logs.some(m => m.includes('失败'))).toBe(true); + expect(logs.some(m => /failed/i.test(m))).toBe(true); }); }); @@ -98,16 +98,30 @@ describe('streamDownloadAndDecrypt', () => { ); expect(result).toEqual({ ok: true }); - expect(logs.some(m => m.includes('下载完成'))).toBe(true); + expect(logs.some(m => m.includes('Download complete'))).toBe(true); expect(chunks.length).toBe(1); expect(new TextDecoder().decode(chunks[0])).toBe('hello from mock'); }); test('throws when no writable and no StreamSaver', async () => { + // jsdom does not load external