You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
6.9 KiB

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeRemoteSource = exports.makeHttpSource = exports.makeXHRSource = exports.makeFetchSource = void 0;
const httputils_js_1 = require("./httputils.js");
const basesource_js_1 = require("./basesource.js");
const blockedsource_js_1 = require("./blockedsource.js");
const fetch_js_1 = require("./client/fetch.js");
const xhr_js_1 = require("./client/xhr.js");
const http_js_1 = require("./client/http.js");
class RemoteSource extends basesource_js_1.BaseSource {
/**
*
* @param {BaseClient} client
* @param {object} headers
* @param {numbers} maxRanges
* @param {boolean} allowFullFile
*/
constructor(client, headers, maxRanges, allowFullFile) {
super();
this.client = client;
this.headers = headers;
this.maxRanges = maxRanges;
this.allowFullFile = allowFullFile;
this._fileSize = null;
}
/**
*
* @param {Slice[]} slices
*/
async fetch(slices, signal) {
// if we allow multi-ranges, split the incoming request into that many sub-requests
// and join them afterwards
if (this.maxRanges >= slices.length) {
return this.fetchSlices(slices, signal);
}
else if (this.maxRanges > 0 && slices.length > 1) {
// TODO: split into multiple multi-range requests
// const subSlicesRequests = [];
// for (let i = 0; i < slices.length; i += this.maxRanges) {
// subSlicesRequests.push(
// this.fetchSlices(slices.slice(i, i + this.maxRanges), signal),
// );
// }
// return (await Promise.all(subSlicesRequests)).flat();
}
// otherwise make a single request for each slice
return Promise.all(slices.map((slice) => this.fetchSlice(slice, signal)));
}
async fetchSlices(slices, signal) {
const response = await this.client.request({
headers: {
...this.headers,
Range: `bytes=${slices
.map(({ offset, length }) => `${offset}-${offset + length}`)
.join(',')}`,
},
signal,
});
if (!response.ok) {
throw new Error('Error fetching data.');
}
else if (response.status === 206) {
const { type, params } = (0, httputils_js_1.parseContentType)(response.getHeader('content-type'));
if (type === 'multipart/byteranges') {
const byteRanges = (0, httputils_js_1.parseByteRanges)(await response.getData(), params.boundary);
this._fileSize = byteRanges[0].fileSize || null;
return byteRanges;
}
const data = await response.getData();
const { start, end, total } = (0, httputils_js_1.parseContentRange)(response.getHeader('content-range'));
this._fileSize = total || null;
const first = [{
data,
offset: start,
length: end - start,
}];
if (slices.length > 1) {
// we requested more than one slice, but got only the first
// unfortunately, some HTTP Servers don't support multi-ranges
// and return onyl the first
// get the rest of the slices and fetch them iteratetively
const others = await Promise.all(slices.slice(1).map((slice) => this.fetchSlice(slice, signal)));
return first.concat(others);
}
return first;
}
else {
if (!this.allowFullFile) {
throw new Error('Server responded with full file');
}
const data = await response.getData();
this._fileSize = data.byteLength;
return [{
data,
offset: 0,
length: data.byteLength,
}];
}
}
async fetchSlice(slice, signal) {
const { offset, length } = slice;
const response = await this.client.request({
headers: {
...this.headers,
Range: `bytes=${offset}-${offset + length}`,
},
signal,
});
// check the response was okay and if the server actually understands range requests
if (!response.ok) {
throw new Error('Error fetching data.');
}
else if (response.status === 206) {
const data = await response.getData();
const { total } = (0, httputils_js_1.parseContentRange)(response.getHeader('content-range'));
this._fileSize = total || null;
return {
data,
offset,
length,
};
}
else {
if (!this.allowFullFile) {
throw new Error('Server responded with full file');
}
const data = await response.getData();
this._fileSize = data.byteLength;
return {
data,
offset: 0,
length: data.byteLength,
};
}
}
get fileSize() {
return this._fileSize;
}
}
function maybeWrapInBlockedSource(source, { blockSize, cacheSize }) {
if (blockSize === null) {
return source;
}
return new blockedsource_js_1.BlockedSource(source, blockSize, cacheSize);
}
function makeFetchSource(url, { headers = {}, credentials, maxRanges = 0, allowFullFile = false, ...blockOptions } = {}) {
const client = new fetch_js_1.FetchClient(url, credentials);
const source = new RemoteSource(client, headers, maxRanges, allowFullFile);
return maybeWrapInBlockedSource(source, blockOptions);
}
exports.makeFetchSource = makeFetchSource;
function makeXHRSource(url, { headers = {}, maxRanges = 0, allowFullFile = false, ...blockOptions } = {}) {
const client = new xhr_js_1.XHRClient(url);
const source = new RemoteSource(client, headers, maxRanges, allowFullFile);
return maybeWrapInBlockedSource(source, blockOptions);
}
exports.makeXHRSource = makeXHRSource;
function makeHttpSource(url, { headers = {}, maxRanges = 0, allowFullFile = false, ...blockOptions } = {}) {
const client = new http_js_1.HttpClient(url);
const source = new RemoteSource(client, headers, maxRanges, allowFullFile);
return maybeWrapInBlockedSource(source, blockOptions);
}
exports.makeHttpSource = makeHttpSource;
/**
*
* @param {string} url
* @param {object} options
*/
function makeRemoteSource(url, { forceXHR = false, ...clientOptions } = {}) {
if (typeof fetch === 'function' && !forceXHR) {
return makeFetchSource(url, clientOptions);
}
if (typeof XMLHttpRequest !== 'undefined') {
return makeXHRSource(url, clientOptions);
}
return makeHttpSource(url, clientOptions);
}
exports.makeRemoteSource = makeRemoteSource;
//# sourceMappingURL=remote.js.map