UNPKG

p2p-media-loader-core

Version:
193 lines 7.83 kB
import { RequestError, } from "./types.js"; export class HttpRequestExecutor { constructor(request, httpConfig, eventTarget) { Object.defineProperty(this, "request", { enumerable: true, configurable: true, writable: true, value: request }); Object.defineProperty(this, "httpConfig", { enumerable: true, configurable: true, writable: true, value: httpConfig }); Object.defineProperty(this, "requestControls", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "abortController", { enumerable: true, configurable: true, writable: true, value: new AbortController() }); Object.defineProperty(this, "expectedBytesLength", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "requestByteRange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onChunkDownloaded", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.onChunkDownloaded = eventTarget.getEventDispatcher("onChunkDownloaded"); const { byteRange } = this.request.segment; if (byteRange) this.requestByteRange = { ...byteRange }; if (request.loadedBytes !== 0) { this.requestByteRange = this.requestByteRange ?? { start: 0 }; this.requestByteRange.start = this.requestByteRange.start + request.loadedBytes; } if (this.request.totalBytes) { this.expectedBytesLength = this.request.totalBytes - this.request.loadedBytes; } this.requestControls = this.request.start({ downloadSource: "http" }, { abort: () => this.abortController.abort("abort"), notReceivingBytesTimeoutMs: this.httpConfig.httpNotReceivingBytesTimeoutMs, }); void this.fetch(); } async fetch() { const { segment } = this.request; try { let request = await this.httpConfig.httpRequestSetup?.(segment.url, segment.byteRange, this.abortController.signal, this.requestByteRange); if (!request) { const headers = new Headers(this.requestByteRange ? { Range: `bytes=${this.requestByteRange.start}-${this.requestByteRange.end ?? ""}`, } : undefined); request = new Request(segment.url, { headers, signal: this.abortController.signal, }); } if (this.abortController.signal.aborted) { throw new DOMException("Request aborted before request fetch", "AbortError"); } const response = await window.fetch(request); this.handleResponseHeaders(response); if (!response.body) return; const { requestControls } = this; requestControls.firstBytesReceived(); const reader = response.body.getReader(); for await (const chunk of readStream(reader)) { this.requestControls.addLoadedChunk(chunk); this.onChunkDownloaded(chunk.byteLength, "http"); } const isValid = (await this.httpConfig.validateHTTPSegment?.(segment.url, segment.byteRange, this.request.data)) ?? true; if (!isValid) { this.request.clearLoadedBytes(); throw new RequestError("http-segment-validation-failed"); } requestControls.completeOnSuccess(); } catch (error) { this.handleError(error); } } handleResponseHeaders(response) { if (!response.ok) { if (response.status === 406) { this.request.clearLoadedBytes(); throw new RequestError("http-bytes-mismatch", response.statusText); } else { throw new RequestError("http-error", response.statusText); } } const { requestByteRange } = this; if (requestByteRange) { if (response.status === 200) { if (this.request.segment.byteRange) { throw new RequestError("http-unexpected-status-code"); } else { this.request.clearLoadedBytes(); } } else { if (response.status !== 206) { throw new RequestError("http-unexpected-status-code", response.statusText); } const contentLengthHeader = response.headers.get("Content-Length"); if (contentLengthHeader && this.expectedBytesLength !== undefined && this.expectedBytesLength !== +contentLengthHeader) { this.request.clearLoadedBytes(); throw new RequestError("http-bytes-mismatch", response.statusText); } const contentRangeHeader = response.headers.get("Content-Range"); const contentRange = contentRangeHeader ? parseContentRangeHeader(contentRangeHeader) : undefined; if (contentRange) { const { from, to } = contentRange; const responseExpectedBytesLength = to !== undefined && from !== undefined ? to - from + 1 : undefined; if ((responseExpectedBytesLength !== undefined && this.expectedBytesLength !== responseExpectedBytesLength) || (from !== undefined && requestByteRange.start !== from) || (to !== undefined && requestByteRange.end !== undefined && requestByteRange.end !== to)) { this.request.clearLoadedBytes(); throw new RequestError("http-bytes-mismatch", response.statusText); } } } } if (response.status === 200 && this.request.totalBytes === undefined) { const contentLengthHeader = response.headers.get("Content-Length"); if (contentLengthHeader) this.request.setTotalBytes(+contentLengthHeader); } } handleError(error) { if (error instanceof Error) { if (error.name !== "abort") return; const httpLoaderError = error instanceof RequestError ? error : new RequestError("http-error", error.message); this.requestControls.abortOnError(httpLoaderError); } } } async function* readStream(reader) { while (true) { const { done, value } = await reader.read(); if (done) break; yield value; } } const rangeHeaderRegex = /^bytes (?:(?:(\d+)|)-(?:(\d+)|)|\*)\/(?:(\d+)|\*)$/; function parseContentRangeHeader(headerValue) { const match = rangeHeaderRegex.exec(headerValue.trim()); if (!match) return; const [, from, to, total] = match; return { from: from ? parseInt(from) : undefined, to: to ? parseInt(to) : undefined, total: total ? parseInt(total) : undefined, }; } //# sourceMappingURL=http-loader.js.map