mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
875 lines
25 KiB
JavaScript
875 lines
25 KiB
JavaScript
(function () {
|
|
const createBlobObjectURL = globalThis.__gopeed_create_blob_object_url;
|
|
const createWritableObjectURL = globalThis.__gopeed_create_writable_stream_object_url;
|
|
const writeWritableObjectURL = globalThis.__gopeed_write_writable_stream_object_url;
|
|
const closeWritableObjectURL = globalThis.__gopeed_close_writable_stream_object_url;
|
|
const abortWritableObjectURL = globalThis.__gopeed_abort_writable_stream_object_url;
|
|
const revokeObjectURL = globalThis.__gopeed_revoke_object_url;
|
|
const fetchOpen = globalThis.__gopeed_fetch_open;
|
|
const fetchRead = globalThis.__gopeed_fetch_read;
|
|
const fetchClose = globalThis.__gopeed_fetch_close;
|
|
const fetchAbort = globalThis.__gopeed_fetch_abort;
|
|
|
|
if (typeof globalThis.ReadableStream === "undefined") {
|
|
class ReadableStreamDefaultController {
|
|
constructor(stream) {
|
|
this._stream = stream;
|
|
}
|
|
|
|
enqueue(chunk) {
|
|
this._stream._enqueue(chunk);
|
|
}
|
|
|
|
close() {
|
|
this._stream._close();
|
|
}
|
|
|
|
error(err) {
|
|
this._stream._error(err);
|
|
}
|
|
}
|
|
|
|
class ReadableStreamDefaultReader {
|
|
constructor(stream) {
|
|
this._stream = stream;
|
|
}
|
|
|
|
read() {
|
|
return this._stream._read();
|
|
}
|
|
|
|
cancel(reason) {
|
|
if (!this._stream) {
|
|
return Promise.resolve();
|
|
}
|
|
return this._stream.cancel(reason);
|
|
}
|
|
|
|
releaseLock() {
|
|
if (this._stream) {
|
|
this._stream._reader = null;
|
|
this._stream.locked = false;
|
|
this._stream = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ReadableStream {
|
|
constructor(source = {}) {
|
|
this._source = source;
|
|
this._queue = [];
|
|
this._waiters = [];
|
|
this._closed = false;
|
|
this._errored = null;
|
|
this._reader = null;
|
|
this._pulling = false;
|
|
this.locked = false;
|
|
this._controller = new ReadableStreamDefaultController(this);
|
|
if (typeof source.start === "function") {
|
|
source.start(this._controller);
|
|
}
|
|
}
|
|
|
|
_enqueue(chunk) {
|
|
if (this._closed || this._errored) {
|
|
return;
|
|
}
|
|
if (this._waiters.length > 0) {
|
|
const waiter = this._waiters.shift();
|
|
waiter.resolve({ done: false, value: chunk });
|
|
return;
|
|
}
|
|
this._queue.push(chunk);
|
|
}
|
|
|
|
_close() {
|
|
this._closed = true;
|
|
while (this._waiters.length > 0) {
|
|
this._waiters.shift().resolve({ done: true, value: undefined });
|
|
}
|
|
}
|
|
|
|
_error(err) {
|
|
this._errored = err || new Error("ReadableStream error");
|
|
while (this._waiters.length > 0) {
|
|
this._waiters.shift().reject(this._errored);
|
|
}
|
|
}
|
|
|
|
_read() {
|
|
if (this._errored) {
|
|
return Promise.reject(this._errored);
|
|
}
|
|
this._markBodyUsed();
|
|
if (this._queue.length > 0) {
|
|
return Promise.resolve({ done: false, value: this._queue.shift() });
|
|
}
|
|
if (this._closed) {
|
|
return Promise.resolve({ done: true, value: undefined });
|
|
}
|
|
this._maybePull();
|
|
if (this._queue.length > 0) {
|
|
return Promise.resolve({ done: false, value: this._queue.shift() });
|
|
}
|
|
if (this._closed) {
|
|
return Promise.resolve({ done: true, value: undefined });
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
this._waiters.push({ resolve, reject });
|
|
this._maybePull();
|
|
});
|
|
}
|
|
|
|
_maybePull() {
|
|
if (this._closed || this._errored || this._pulling) {
|
|
return;
|
|
}
|
|
if (this._queue.length > 0) {
|
|
return;
|
|
}
|
|
if (!this._source || typeof this._source.pull !== "function") {
|
|
return;
|
|
}
|
|
this._pulling = true;
|
|
Promise.resolve(this._source.pull(this._controller))
|
|
.catch((err) => {
|
|
this._error(err);
|
|
})
|
|
.finally(() => {
|
|
this._pulling = false;
|
|
if (!this._closed && !this._errored && this._queue.length === 0 && this._waiters.length > 0) {
|
|
this._maybePull();
|
|
}
|
|
});
|
|
}
|
|
|
|
_markBodyUsed() {
|
|
if (typeof this.__gopeedMarkBodyUsed === "function") {
|
|
this.__gopeedMarkBodyUsed();
|
|
}
|
|
}
|
|
|
|
getReader() {
|
|
if (this.locked) {
|
|
throw new TypeError("ReadableStream is locked");
|
|
}
|
|
this.locked = true;
|
|
this._reader = new ReadableStreamDefaultReader(this);
|
|
return this._reader;
|
|
}
|
|
|
|
cancel(reason) {
|
|
this._markBodyUsed();
|
|
if (this._source && typeof this._source.cancel === "function") {
|
|
return Promise.resolve(this._source.cancel(reason)).then(() => {
|
|
this._close();
|
|
});
|
|
}
|
|
this._close();
|
|
return Promise.resolve();
|
|
}
|
|
|
|
async pipeTo(dest) {
|
|
const reader = this.getReader();
|
|
const writer = dest.getWriter();
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
await writer.close();
|
|
break;
|
|
}
|
|
await writer.write(value);
|
|
}
|
|
} catch (err) {
|
|
await writer.abort(err);
|
|
throw err;
|
|
} finally {
|
|
reader.releaseLock();
|
|
writer.releaseLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
globalThis.ReadableStream = ReadableStream;
|
|
}
|
|
|
|
if (typeof globalThis.WritableStream === "undefined") {
|
|
class WritableStreamDefaultWriter {
|
|
constructor(stream) {
|
|
if (stream.locked) {
|
|
throw new TypeError("WritableStream is locked");
|
|
}
|
|
this._stream = stream;
|
|
this._stream.locked = true;
|
|
}
|
|
|
|
write(chunk) {
|
|
return this._stream._write(chunk);
|
|
}
|
|
|
|
close() {
|
|
return this._stream._close();
|
|
}
|
|
|
|
abort(reason) {
|
|
return this._stream._abort(reason);
|
|
}
|
|
|
|
releaseLock() {
|
|
if (this._stream) {
|
|
this._stream.locked = false;
|
|
this._stream = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
class WritableStream {
|
|
constructor(sink = {}) {
|
|
this._sink = sink;
|
|
this._observers = [];
|
|
this._state = "writable";
|
|
this.locked = false;
|
|
}
|
|
|
|
_targets() {
|
|
return [this._sink, ...this._observers];
|
|
}
|
|
|
|
_write(chunk) {
|
|
if (this._state !== "writable") {
|
|
return Promise.reject(new TypeError("WritableStream is not writable"));
|
|
}
|
|
let chain = Promise.resolve();
|
|
for (const target of this._targets()) {
|
|
if (target && typeof target.write === "function") {
|
|
chain = chain.then(() => target.write(chunk));
|
|
}
|
|
}
|
|
return chain;
|
|
}
|
|
|
|
_close() {
|
|
if (this._state !== "writable") {
|
|
return Promise.reject(new TypeError("WritableStream is not writable"));
|
|
}
|
|
this._state = "closed";
|
|
let chain = Promise.resolve();
|
|
for (const target of this._targets()) {
|
|
if (target && typeof target.close === "function") {
|
|
chain = chain.then(() => target.close());
|
|
}
|
|
}
|
|
return chain;
|
|
}
|
|
|
|
_abort(reason) {
|
|
if (this._state === "errored") {
|
|
return Promise.resolve();
|
|
}
|
|
this._state = "errored";
|
|
let chain = Promise.resolve();
|
|
for (const target of this._targets()) {
|
|
if (target && typeof target.abort === "function") {
|
|
chain = chain.then(() => target.abort(reason));
|
|
}
|
|
}
|
|
return chain;
|
|
}
|
|
|
|
_addObserver(observer) {
|
|
this._observers.push(observer);
|
|
}
|
|
|
|
getWriter() {
|
|
return new WritableStreamDefaultWriter(this);
|
|
}
|
|
}
|
|
|
|
globalThis.WritableStream = WritableStream;
|
|
}
|
|
|
|
if (typeof globalThis.TransformStream === "undefined") {
|
|
class TransformStream {
|
|
constructor(transformer = {}) {
|
|
let readableController;
|
|
this.readable = new ReadableStream({
|
|
start(controller) {
|
|
readableController = controller;
|
|
}
|
|
});
|
|
this.writable = new WritableStream({
|
|
async write(chunk) {
|
|
if (typeof transformer.transform === "function") {
|
|
await transformer.transform(chunk, readableController);
|
|
return;
|
|
}
|
|
readableController.enqueue(chunk);
|
|
},
|
|
async close() {
|
|
if (typeof transformer.flush === "function") {
|
|
await transformer.flush(readableController);
|
|
}
|
|
readableController.close();
|
|
},
|
|
async abort(reason) {
|
|
if (typeof transformer.abort === "function") {
|
|
await transformer.abort(reason);
|
|
}
|
|
readableController.error(reason || new Error("TransformStream aborted"));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
globalThis.TransformStream = TransformStream;
|
|
}
|
|
|
|
if (typeof globalThis.ByteLengthQueuingStrategy === "undefined") {
|
|
globalThis.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
|
|
constructor({ highWaterMark }) {
|
|
this.highWaterMark = highWaterMark;
|
|
}
|
|
|
|
size(chunk) {
|
|
if (typeof chunk === "string") {
|
|
return chunk.length;
|
|
}
|
|
if (chunk && typeof chunk.byteLength === "number") {
|
|
return chunk.byteLength;
|
|
}
|
|
return 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
if (typeof globalThis.CountQueuingStrategy === "undefined") {
|
|
globalThis.CountQueuingStrategy = class CountQueuingStrategy {
|
|
constructor({ highWaterMark }) {
|
|
this.highWaterMark = highWaterMark;
|
|
}
|
|
|
|
size() {
|
|
return 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
function toUint8Array(chunk) {
|
|
if (chunk == null) {
|
|
return new Uint8Array(0);
|
|
}
|
|
if (chunk instanceof Uint8Array) {
|
|
return chunk;
|
|
}
|
|
if (typeof chunk === "string") {
|
|
return new TextEncoder().encode(chunk);
|
|
}
|
|
if (chunk instanceof ArrayBuffer) {
|
|
return new Uint8Array(chunk);
|
|
}
|
|
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView && ArrayBuffer.isView(chunk)) {
|
|
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
}
|
|
if (typeof Blob !== "undefined" && chunk instanceof Blob) {
|
|
if (chunk._buffer instanceof Uint8Array) {
|
|
return chunk._buffer;
|
|
}
|
|
}
|
|
return new Uint8Array(0);
|
|
}
|
|
|
|
function createBodyReadableStream(owner) {
|
|
return new ReadableStream({
|
|
start(controller) {
|
|
Promise.resolve().then(async () => {
|
|
owner.bodyUsed = true;
|
|
if (owner._noBody) {
|
|
controller.close();
|
|
return;
|
|
}
|
|
if (owner._bodyArrayBuffer) {
|
|
controller.enqueue(toUint8Array(owner._bodyArrayBuffer));
|
|
controller.close();
|
|
return;
|
|
}
|
|
if (owner._bodyBlob) {
|
|
const data = await owner._bodyBlob.arrayBuffer();
|
|
controller.enqueue(new Uint8Array(data));
|
|
controller.close();
|
|
return;
|
|
}
|
|
if (owner._bodyText != null) {
|
|
controller.enqueue(toUint8Array(owner._bodyText));
|
|
controller.close();
|
|
return;
|
|
}
|
|
if (owner._bodyInit != null) {
|
|
controller.enqueue(toUint8Array(owner._bodyInit));
|
|
}
|
|
controller.close();
|
|
}).catch((err) => {
|
|
controller.error(err);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
async function readAllFromStream(stream, asText) {
|
|
if (!stream) {
|
|
return asText ? "" : new Uint8Array(0);
|
|
}
|
|
const reader = stream.getReader();
|
|
const chunks = [];
|
|
let total = 0;
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
break;
|
|
}
|
|
const chunk = toUint8Array(value);
|
|
chunks.push(chunk);
|
|
total += chunk.byteLength;
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
const merged = new Uint8Array(total);
|
|
let offset = 0;
|
|
for (const chunk of chunks) {
|
|
merged.set(chunk, offset);
|
|
offset += chunk.byteLength;
|
|
}
|
|
if (asText) {
|
|
return new TextDecoder().decode(merged);
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
function createStreamBackedBlob(stream, contentType, size) {
|
|
const blob = new Blob([], { type: contentType || "" });
|
|
if (Number.isFinite(size) && size >= 0) {
|
|
try {
|
|
Object.defineProperty(blob, "size", {
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: size,
|
|
});
|
|
} catch (_) {
|
|
}
|
|
}
|
|
blob.__gopeedStreamBackedBlob = true;
|
|
blob.__gopeedBodyStream = stream;
|
|
blob.stream = function () {
|
|
const bodyStream = this.__gopeedBodyStream;
|
|
this.__gopeedBodyStream = null;
|
|
if (bodyStream instanceof ReadableStream) {
|
|
return bodyStream;
|
|
}
|
|
return new Blob([], { type: this.type || "" }).stream();
|
|
};
|
|
blob.arrayBuffer = async function () {
|
|
const bytes = await readAllFromStream(this.stream(), false);
|
|
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
};
|
|
blob.text = async function () {
|
|
return readAllFromStream(this.stream(), true);
|
|
};
|
|
return blob;
|
|
}
|
|
|
|
function attachResponseStreaming(response, stream) {
|
|
response.__gopeedBodyStream = stream;
|
|
response.__gopeedBodyConsumed = false;
|
|
const ensureUnused = () => {
|
|
if (response.__gopeedBodyConsumed) {
|
|
throw new TypeError("Already read");
|
|
}
|
|
};
|
|
const markBodyUsed = () => {
|
|
ensureUnused();
|
|
response.__gopeedBodyConsumed = true;
|
|
response.bodyUsed = true;
|
|
};
|
|
if (stream) {
|
|
stream.__gopeedMarkBodyUsed = () => {
|
|
if (!response.__gopeedBodyConsumed) {
|
|
response.__gopeedBodyConsumed = true;
|
|
response.bodyUsed = true;
|
|
}
|
|
};
|
|
}
|
|
response.text = async function () {
|
|
ensureUnused();
|
|
markBodyUsed();
|
|
return readAllFromStream(stream, true);
|
|
};
|
|
response.arrayBuffer = async function () {
|
|
ensureUnused();
|
|
markBodyUsed();
|
|
const bytes = await readAllFromStream(stream, false);
|
|
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
};
|
|
response.blob = async function () {
|
|
ensureUnused();
|
|
markBodyUsed();
|
|
const contentType = this.headers && this.headers.get ? (this.headers.get("content-type") || "") : "";
|
|
const contentLength = this.headers && this.headers.get ? parseInt(this.headers.get("content-length") || "", 10) : NaN;
|
|
return createStreamBackedBlob(stream, contentType, contentLength);
|
|
};
|
|
response.json = async function () {
|
|
const text = await this.text();
|
|
return JSON.parse(text);
|
|
};
|
|
return response;
|
|
}
|
|
|
|
if (typeof globalThis.Response === "function") {
|
|
const responseProto = globalThis.Response.prototype;
|
|
const bodyDescriptor = Object.getOwnPropertyDescriptor(responseProto, "body");
|
|
if (!bodyDescriptor || typeof bodyDescriptor.get !== "function") {
|
|
Object.defineProperty(responseProto, "body", {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get() {
|
|
if (this.__gopeedBodyStream) {
|
|
return this.__gopeedBodyStream;
|
|
}
|
|
if (!this.__gopeedBodyStream) {
|
|
this.__gopeedBodyStream = createBodyReadableStream(this);
|
|
}
|
|
return this.__gopeedBodyStream;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const originalCreateObjectURL = typeof URL.createObjectURL === "function"
|
|
? URL.createObjectURL.bind(URL)
|
|
: null;
|
|
const originalRevokeObjectURL = typeof URL.revokeObjectURL === "function"
|
|
? URL.revokeObjectURL.bind(URL)
|
|
: null;
|
|
const resumableReadableObjectURLs = new Map();
|
|
const activeReadableObjectURLs = new Map();
|
|
const readableObjectURLYieldBytes = 256 * 1024;
|
|
|
|
function isIgnorableGBlobObjectURLError(error) {
|
|
const message = error == null ? "" : String(error && error.message ? error.message : error);
|
|
return message.indexOf("gblob source revoked") >= 0 ||
|
|
message.indexOf("gblob source not found") >= 0 ||
|
|
message.indexOf("gblob source closed") >= 0 ||
|
|
message.indexOf("gblob source aborted") >= 0;
|
|
}
|
|
|
|
function getValueTypeName(value) {
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
if (value === undefined) {
|
|
return "undefined";
|
|
}
|
|
if (value && value.constructor && typeof value.constructor.name === "string" && value.constructor.name) {
|
|
return value.constructor.name;
|
|
}
|
|
return typeof value;
|
|
}
|
|
|
|
function toReadableStreamReader(value, sourceLabel) {
|
|
if (value instanceof ReadableStream) {
|
|
return value.getReader();
|
|
}
|
|
throw new TypeError(sourceLabel + " must return a ReadableStream, got " + getValueTypeName(value));
|
|
}
|
|
|
|
function describeObjectURLValue(value) {
|
|
if (value && value.__gopeedStreamBackedBlob) {
|
|
const stream = value.__gopeedBodyStream;
|
|
value.__gopeedBodyStream = null;
|
|
if (stream instanceof ReadableStream) {
|
|
return {
|
|
kind: "readable",
|
|
initialReadable: stream,
|
|
openReadable: null,
|
|
sourceLabel: "URL.createObjectURL Response blob stream",
|
|
};
|
|
}
|
|
}
|
|
if (value instanceof Blob) {
|
|
return {
|
|
kind: "blob",
|
|
value,
|
|
};
|
|
}
|
|
if (value instanceof ReadableStream) {
|
|
return {
|
|
kind: "readable",
|
|
initialReadable: value,
|
|
openReadable: null,
|
|
sourceLabel: "URL.createObjectURL ReadableStream",
|
|
};
|
|
}
|
|
if (typeof value === "function") {
|
|
return {
|
|
kind: "opener",
|
|
initialReadable: null,
|
|
openReadable: value,
|
|
sourceLabel: "URL.createObjectURL opener function",
|
|
};
|
|
}
|
|
return {
|
|
kind: "other",
|
|
value,
|
|
};
|
|
}
|
|
|
|
function startReadableObjectURL(url, state) {
|
|
activeReadableObjectURLs.set(url, state);
|
|
setTimeout(() => {
|
|
void pumpReadableObjectURL(url, state);
|
|
}, 0);
|
|
}
|
|
|
|
function releaseReader(reader, reason) {
|
|
if (!reader) {
|
|
return;
|
|
}
|
|
try {
|
|
if (reason !== undefined && typeof reader.cancel === "function") {
|
|
const result = reader.cancel(reason);
|
|
if (result && typeof result.catch === "function") {
|
|
result.catch(function () {});
|
|
}
|
|
}
|
|
} catch (_) {
|
|
}
|
|
if (typeof reader.releaseLock === "function") {
|
|
try {
|
|
reader.releaseLock();
|
|
} catch (_) {
|
|
}
|
|
}
|
|
}
|
|
|
|
function releaseActiveReadable(url, reason) {
|
|
const active = activeReadableObjectURLs.get(url);
|
|
if (!active) {
|
|
return;
|
|
}
|
|
activeReadableObjectURLs.delete(url);
|
|
active.cancelled = true;
|
|
releaseReader(active.reader, reason);
|
|
}
|
|
|
|
async function pumpReadableObjectURL(url, state) {
|
|
let reader = null;
|
|
try {
|
|
let source;
|
|
if (state.initialReadable) {
|
|
source = state.initialReadable;
|
|
state.initialReadable = null;
|
|
} else {
|
|
if (typeof state.openReadable !== "function") {
|
|
throw new Error("gblob readable stream is not reopenable");
|
|
}
|
|
source = await state.openReadable(state.offset);
|
|
}
|
|
reader = toReadableStreamReader(source, state.sourceLabel);
|
|
if (activeReadableObjectURLs.get(url) !== state || state.cancelled) {
|
|
releaseReader(reader, "stale gblob producer");
|
|
reader = null;
|
|
return;
|
|
}
|
|
state.reader = reader;
|
|
let bytesSinceYield = 0;
|
|
while (!state.cancelled) {
|
|
const current = activeReadableObjectURLs.get(url);
|
|
if (current !== state) {
|
|
releaseReader(reader, "stale gblob producer");
|
|
reader = null;
|
|
return;
|
|
}
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
if (activeReadableObjectURLs.get(url) === state) {
|
|
activeReadableObjectURLs.delete(url);
|
|
await closeWritableObjectURL(url);
|
|
}
|
|
releaseReader(reader);
|
|
reader = null;
|
|
return;
|
|
}
|
|
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
if (activeReadableObjectURLs.get(url) !== state || state.cancelled) {
|
|
releaseReader(reader, "stale gblob producer");
|
|
reader = null;
|
|
return;
|
|
}
|
|
await writeWritableObjectURL(url, chunk);
|
|
bytesSinceYield += chunk.byteLength;
|
|
if (bytesSinceYield >= readableObjectURLYieldBytes) {
|
|
bytesSinceYield = 0;
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
}
|
|
releaseReader(reader, "gblob producer cancelled");
|
|
reader = null;
|
|
} catch (error) {
|
|
if (isIgnorableGBlobObjectURLError(error)) {
|
|
if (activeReadableObjectURLs.get(url) === state) {
|
|
activeReadableObjectURLs.delete(url);
|
|
}
|
|
if (reader) {
|
|
releaseReader(reader, "gblob source closed");
|
|
reader = null;
|
|
}
|
|
return;
|
|
}
|
|
if (activeReadableObjectURLs.get(url) === state) {
|
|
activeReadableObjectURLs.delete(url);
|
|
try {
|
|
await abortWritableObjectURL(url, error == null ? "" : String(error && error.message ? error.message : error));
|
|
} catch (_) {
|
|
}
|
|
}
|
|
if (reader) {
|
|
releaseReader(reader, error);
|
|
reader = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
globalThis.__gopeed_open_writable_stream_object_url = function (url, offset) {
|
|
const openReadable = resumableReadableObjectURLs.get(url);
|
|
if (typeof openReadable !== "function") {
|
|
throw new Error("gblob resumable opener not found");
|
|
}
|
|
releaseActiveReadable(url, "gblob source reopened");
|
|
startReadableObjectURL(url, {
|
|
initialReadable: null,
|
|
openReadable,
|
|
sourceLabel: "URL.createObjectURL opener function",
|
|
offset: Number(offset) || 0,
|
|
reader: null,
|
|
cancelled: false,
|
|
});
|
|
};
|
|
|
|
URL.createObjectURL = function (value) {
|
|
const described = describeObjectURLValue(value);
|
|
if (described.kind === "blob") {
|
|
return createBlobObjectURL(described.value._buffer, described.value.type || "");
|
|
}
|
|
if (described.kind === "readable") {
|
|
const url = createWritableObjectURL(false);
|
|
startReadableObjectURL(url, {
|
|
initialReadable: described.initialReadable,
|
|
openReadable: null,
|
|
sourceLabel: described.sourceLabel,
|
|
offset: 0,
|
|
reader: null,
|
|
cancelled: false,
|
|
});
|
|
return url;
|
|
}
|
|
if (described.kind === "opener") {
|
|
const url = createWritableObjectURL(true);
|
|
resumableReadableObjectURLs.set(url, described.openReadable);
|
|
return url;
|
|
}
|
|
throw new TypeError("Unsupported object type for URL.createObjectURL: " + getValueTypeName(described.value) + ". Expected Blob, ReadableStream, or opener function");
|
|
};
|
|
|
|
URL.revokeObjectURL = function (url) {
|
|
if (typeof url === "string" && url.indexOf("gblob:") === 0) {
|
|
resumableReadableObjectURLs.delete(url);
|
|
releaseActiveReadable(url, "gblob source revoked");
|
|
revokeObjectURL(url);
|
|
return;
|
|
}
|
|
if (originalRevokeObjectURL) {
|
|
originalRevokeObjectURL(url);
|
|
}
|
|
};
|
|
|
|
const originalFetch = typeof globalThis.fetch === "function"
|
|
? globalThis.fetch.bind(globalThis)
|
|
: null;
|
|
|
|
if (typeof fetchOpen === "function") {
|
|
globalThis.fetch = async function (input, init) {
|
|
const request = new Request(input, init);
|
|
if (originalFetch && (request.method === "HEAD" || request.redirect === "manual" || request.redirect === "error")) {
|
|
return originalFetch(input, init);
|
|
}
|
|
let body = null;
|
|
if (request._bodyFormData) {
|
|
body = request._bodyFormData;
|
|
} else if (request._bodyArrayBuffer) {
|
|
body = request._bodyArrayBuffer;
|
|
} else if (request._bodyBlob) {
|
|
body = await request._bodyBlob.arrayBuffer();
|
|
} else if (request._bodyInit != null && typeof request._bodyInit === "object") {
|
|
body = request._bodyInit;
|
|
} else if (request._bodyText != null) {
|
|
body = request._bodyText;
|
|
} else if (request._bodyInit != null) {
|
|
body = request._bodyInit;
|
|
}
|
|
const headers = [];
|
|
request.headers.forEach((value, key) => {
|
|
headers.push([key, value]);
|
|
});
|
|
let meta;
|
|
try {
|
|
meta = fetchOpen({
|
|
url: request.url,
|
|
method: request.method,
|
|
headers,
|
|
body,
|
|
redirect: request.redirect,
|
|
credentials: request.credentials
|
|
});
|
|
} catch (error) {
|
|
throw error instanceof Error ? error : new TypeError(String(error));
|
|
}
|
|
const stream = new ReadableStream({
|
|
pull(controller) {
|
|
let chunk;
|
|
try {
|
|
chunk = fetchRead(meta.id, 64 * 1024);
|
|
} catch (error) {
|
|
fetchClose(meta.id);
|
|
controller.error(error instanceof Error ? error : new TypeError(String(error)));
|
|
return;
|
|
}
|
|
if (chunk == null) {
|
|
fetchClose(meta.id);
|
|
controller.close();
|
|
return;
|
|
}
|
|
const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
if (bytes.byteLength === 0) {
|
|
fetchClose(meta.id);
|
|
controller.close();
|
|
return;
|
|
}
|
|
controller.enqueue(bytes);
|
|
},
|
|
cancel(reason) {
|
|
fetchAbort(meta.id, reason == null ? "" : String(reason));
|
|
}
|
|
});
|
|
const response = new Response(null, {
|
|
status: meta.status,
|
|
statusText: meta.statusText,
|
|
headers: meta.headers,
|
|
url: meta.url
|
|
});
|
|
return attachResponseStreaming(response, stream);
|
|
};
|
|
globalThis.fetch.__gopeedOriginalFetch = originalFetch;
|
|
}
|
|
})();
|