native-file-system-adapter-ts
Version:
Native File System API
163 lines • 5.82 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileHandleDownloader = void 0;
const errors_1 = require("../errors");
const { GONE } = errors_1.errors;
const isSafari = /constructor/i.test(window.HTMLElement) || window.safari || window.WebKitPoint;
class FileHandleDownloader {
constructor(name = 'unkown') {
this.name = name;
this.kind = 'file';
this.writable = true;
this.readable = false;
this._deleted = false;
}
async getFile() {
throw new Error(...GONE);
}
async isSameEntry(other) {
return this === other;
}
async remove() {
this._deleted = true;
}
async createWritable(options = {}) {
var _a;
if (this._deleted)
throw new Error(...GONE);
const sw = await navigator.serviceWorker.getRegistration();
const link = document.createElement('a');
const ts = new TransformStream();
const sink = ts.writable;
link.download = this.name;
if (isSafari || !sw) {
/** @type {Blob[]} */
let chunks = [];
ts.readable.pipeTo(new WritableStream({
write(chunk) {
chunks.push(new Blob([chunk]));
},
close() {
const blob = new Blob(chunks, {
type: 'application/octet-stream; charset=utf-8',
});
chunks = [];
link.href = URL.createObjectURL(blob);
link.click();
setTimeout(() => URL.revokeObjectURL(link.href), 10000);
},
}));
}
else {
const { writable, readablePort } = new RemoteWritableStream();
// Make filename RFC5987 compatible
const fileName = encodeURIComponent(this.name).replace(/['()]/g, escape).replace(/\*/g, '%2A');
const headers = {
'content-disposition': "attachment; filename*=UTF-8''" + fileName,
'content-type': 'application/octet-stream; charset=utf-8',
...(options.size ? { 'content-length': options.size } : {}),
};
const keepAlive = setTimeout(() => { var _a; return (_a = sw.active) === null || _a === void 0 ? void 0 : _a.postMessage(0); }, 10000);
ts.readable
.pipeThrough(new TransformStream({
transform(chunk, ctrl) {
var _a;
if (chunk instanceof Uint8Array)
return ctrl.enqueue(chunk);
const reader = (_a = new Response(chunk).body) === null || _a === void 0 ? void 0 : _a.getReader();
const pump = () => reader === null || reader === void 0 ? void 0 : reader.read().then((e) => (e.done ? 0 : pump(ctrl.enqueue(e.value))));
return pump();
},
}))
.pipeTo(writable)
.finally(() => {
clearInterval(keepAlive);
});
// Transfer the stream to service worker
(_a = sw.active) === null || _a === void 0 ? void 0 : _a.postMessage({
url: sw.scope + fileName,
headers,
readablePort,
}, [readablePort]);
// Trigger the download with a hidden iframe
const iframe = document.createElement('iframe');
iframe.hidden = true;
iframe.src = sw.scope + fileName;
document.body.appendChild(iframe);
}
return sink.getWriter();
}
}
exports.FileHandleDownloader = FileHandleDownloader;
const WRITE = 0;
const PULL = 0;
const ERROR = 1;
const ABORT = 1;
const CLOSE = 2;
class MessagePortSink {
constructor(port) {
port.onmessage = (event) => this._onMessage(event.data);
this._port = port;
this._resetReady();
}
start(controller) {
this._controller = controller;
// Apply initial backpressure
return this._readyPromise;
}
write(chunk) {
const message = { type: WRITE, chunk };
// Send chunk
this._port.postMessage(message, [chunk.buffer]);
// Assume backpressure after every write, until sender pulls
this._resetReady();
// Apply backpressure
return this._readyPromise;
}
close() {
this._port.postMessage({ type: CLOSE });
this._port.close();
}
abort(reason) {
this._port.postMessage({ type: ABORT, reason });
this._port.close();
}
_onMessage(message) {
if (message.type === PULL)
this._resolveReady();
if (message.type === ERROR)
this._onError(message.reason);
}
_onError(reason) {
this._controller.error(reason);
this._rejectReady(reason);
this._port.close();
}
_resetReady() {
this._readyPromise = new Promise((resolve, reject) => {
this._readyResolve = resolve;
this._readyReject = reject;
});
this._readyPending = true;
}
_resolveReady() {
this._readyResolve();
this._readyPending = false;
}
_rejectReady(reason) {
if (!this._readyPending)
this._resetReady();
// eslint-disable-next-line @typescript-eslint/no-empty-function
this._readyPromise.catch(() => { });
this._readyReject(reason);
this._readyPending = false;
}
}
class RemoteWritableStream {
constructor() {
const channel = new MessageChannel();
this.readablePort = channel.port1;
this.writable = new WritableStream(new MessagePortSink(channel.port2));
}
}
//# sourceMappingURL=downloader.js.map