Skip to content

Commit 8c372b9

Browse files
committed
chore: remove streamsaver dependency
1 parent 35c9777 commit 8c372b9

File tree

3 files changed

+107
-65
lines changed

3 files changed

+107
-65
lines changed

web-app/package-lock.json

Lines changed: 41 additions & 54 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-app/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
"clsx": "^2.1.1",
2222
"native-file-system-adapter": "^3.0.1",
2323
"react": "^19.0.0",
24-
"react-dom": "^19.0.0",
25-
"streamsaver": "^2.0.6"
24+
"react-dom": "^19.0.0"
2625
},
2726
"devDependencies": {
2827
"@eslint/eslintrc": "^3.3.0",
@@ -31,7 +30,6 @@
3130
"@rollup/plugin-inject": "^5.0.5",
3231
"@types/react": "^19.0.10",
3332
"@types/react-dom": "^19.0.4",
34-
"@types/streamsaver": "^2.0.5",
3533
"@types/wicg-file-system-access": "^2023.10.5",
3634
"@vitejs/plugin-react": "^4.3.4",
3735
"@vitest/ui": "^3.0.7",

web-app/src/App.tsx

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,80 @@
11
import { clsx } from 'clsx';
22
import { useState, useEffect, type ChangeEvent } from 'react';
3-
import streamsaver from 'streamsaver';
43
import { showSaveFilePicker } from 'native-file-system-adapter';
54
import './App.css';
65
import { type Chunker, type Source, OpenTDF } from '@opentdf/sdk';
76
import { type SessionInformation, OidcClient } from './session.js';
87
import { c } from './config.js';
98

10-
async function toFile(
9+
/**
10+
* Downloads a ReadableStream as a file by collecting its data and triggering a browser download.
11+
* Supports aborting via AbortSignal in options.
12+
*
13+
* @param stream - The ReadableStream of Uint8Array data to download as a file.
14+
* @param filename - The name for the downloaded file (default: 'download.tdf').
15+
* @param options - Optional StreamPipeOptions, supports AbortSignal for cancellation.
16+
* @returns Promise that resolves when the download is triggered or rejects if aborted.
17+
*/
18+
export async function toFile(
1119
stream: ReadableStream<Uint8Array>,
12-
filepath = 'download.tdf',
20+
filename = 'download.tdf',
1321
options?: StreamPipeOptions
1422
): Promise<void> {
15-
const fileStream = streamsaver.createWriteStream(filepath, {
16-
writableStrategy: { highWaterMark: 1 },
17-
readableStrategy: { highWaterMark: 1 },
18-
});
23+
// Get a reader for the stream
24+
const reader = stream.getReader();
25+
const chunks: Uint8Array[] = [];
26+
let done = false;
27+
let aborted = false;
28+
const signal = options?.signal;
29+
let abortHandler: (() => void) | undefined;
1930

20-
return stream.pipeTo(fileStream, options);
31+
// Setup abort handling if a signal is provided
32+
if (signal) {
33+
if (signal.aborted) {
34+
throw new DOMException('Aborted', 'AbortError');
35+
}
36+
abortHandler = () => {
37+
aborted = true;
38+
reader.cancel();
39+
};
40+
signal.addEventListener('abort', abortHandler);
41+
}
42+
try {
43+
// Read the stream chunk by chunk
44+
while (!done) {
45+
if (aborted) {
46+
throw new DOMException('Aborted', 'AbortError');
47+
}
48+
const { value, done: streamDone } = await reader.read();
49+
if (value) {
50+
chunks.push(value); // Collect each chunk
51+
}
52+
done = streamDone;
53+
}
54+
} finally {
55+
// Clean up abort event listener
56+
if (signal && abortHandler) {
57+
signal.removeEventListener('abort', abortHandler);
58+
}
59+
}
60+
if (aborted) {
61+
throw new DOMException('Aborted', 'AbortError');
62+
}
63+
// Create a Blob from the collected chunks
64+
const blob = new Blob(chunks);
65+
// Create a temporary object URL for the Blob
66+
const url = URL.createObjectURL(blob);
67+
// Create an anchor element and trigger the download
68+
const a = document.createElement('a');
69+
a.href = url;
70+
a.download = filename;
71+
document.body.appendChild(a);
72+
a.click();
73+
// Clean up the anchor and object URL after download is triggered
74+
setTimeout(() => {
75+
document.body.removeChild(a);
76+
URL.revokeObjectURL(url);
77+
}, 0);
2178
}
2279

2380
function decryptedFileName(encryptedFileName: string): string {

0 commit comments

Comments
 (0)