UNPKG

@mlightcad/common

Version:

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![npm version](https://img.shields.io/npm/v/@mlightcad/common.svg)](https://www.npmjs.com/package/@mlightcad/common)

275 lines 11.2 kB
/** * @fileoverview HTTP-based file loader implementation for the AutoCAD Common library. * * This module provides a concrete implementation of the loader interface using * the Fetch API for loading files over HTTP. Supports various response types * and provides comprehensive error handling. * * @module AcCmFileLoader * @version 1.0.0 */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import { AcCmLoader } from './AcCmLoader'; /** * @internal */ var loading = {}; /** * Custom error class for HTTP-related failures. * * @internal */ var HttpError = /** @class */ (function (_super) { __extends(HttpError, _super); /** * Creates a new HttpError. * * @param {string} message - The error message. * @param {Response} response - The HTTP response that caused the error. */ function HttpError(message, response) { var _this = _super.call(this, message) || this; _this.response = response; return _this; } return HttpError; }(Error)); /** * HTTP-based file loader using the Fetch API. * * A low-level class for loading resources over HTTP, used internally by most loaders. * It can also be used directly to load any file type that does not have a specialized loader. * * Supports various response types, custom MIME types, and provides comprehensive error handling * with automatic retry mechanisms for failed requests. * * @example * ```typescript * import { AcCmFileLoader } from './AcCmFileLoader' * * const loader = new AcCmFileLoader() * * // Load a text file * loader.load( * 'data.txt', * (data) => console.log('Loaded:', data), * (progress) => console.log('Progress:', progress.loaded / progress.total), * (error) => console.error('Error:', error) * ) * * // Load binary data * loader.setResponseType('arraybuffer') * loader.load('file.dwg', (arrayBuffer) => { * // Process binary data * }) * ``` */ var AcCmFileLoader = /** @class */ (function (_super) { __extends(AcCmFileLoader, _super); /** * Create a new AcCmFileLoader instance. * @param manager The loadingManager for the loader to use. Default is DefaultLoadingManager. */ function AcCmFileLoader(manager) { return _super.call(this, manager) || this; } /** * Load the URL and pass the response to the onLoad function. * @param url The path or URL to the file. This can also be a Data URI. * @param onLoad (optional) — Will be called when loading completes. * @param onProgress (optional) — Will be called while load progresses. * @param onError (optional) — Will be called if an error occurs. */ AcCmFileLoader.prototype.load = function (url, onLoad, onProgress, onError) { var _this = this; if (url === undefined) url = ''; if (this.path !== undefined) url = this.path + url; url = this.manager.resolveURL(url); // Check if request is duplicate if (loading[url] !== undefined) { loading[url].push({ onLoad: onLoad, onProgress: onProgress, onError: onError }); return; } // Initialise array for duplicate requests loading[url] = []; loading[url].push({ onLoad: onLoad, onProgress: onProgress, onError: onError }); // create request var req = new Request(url, { headers: new Headers(this.requestHeader), credentials: this.withCredentials ? 'include' : 'same-origin' // An abort controller could be added within a future PR }); // record states ( avoid data race ) var mimeType = this.mimeType; var responseType = this.responseType; // start the fetch fetch(req) .then(function (response) { var _a; if (response.status === 200 || response.status === 0) { // Some browsers return HTTP Status 0 when using non-http protocol // e.g. 'file://' or 'data://'. Handle as success. if (response.status === 0) { console.warn('HTTP Status 0 received.'); } // Workaround: Checking if response.body === undefined for Alipay browser #23548 if (typeof ReadableStream === 'undefined' || response.body === undefined || ((_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader) === undefined) { return response; } var callbacks_1 = loading[url]; var reader_1 = response.body.getReader(); // Nginx needs X-File-Size check // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content var contentLength = response.headers.get('X-File-Size') || response.headers.get('Content-Length'); var total_1 = contentLength ? parseInt(contentLength) : 0; var lengthComputable_1 = total_1 !== 0; var loaded_1 = 0; // periodically read data into the new stream tracking while download progress var stream = new ReadableStream({ start: function (controller) { readData(); function readData() { reader_1.read().then(function (_a) { var done = _a.done, value = _a.value; if (done) { controller.close(); } else { loaded_1 += value.byteLength; var event_1 = new ProgressEvent('progress', { lengthComputable: lengthComputable_1, loaded: loaded_1, total: total_1 }); for (var i = 0, il = callbacks_1.length; i < il; i++) { var callback = callbacks_1[i]; if (callback.onProgress) callback.onProgress(event_1); } controller.enqueue(value); readData(); } }, function (e) { controller.error(e); }); } } }); return new Response(stream); } else { throw new HttpError("fetch for \"".concat(response.url, "\" responded with ").concat(response.status, ": ").concat(response.statusText), response); } }) .then(function (response) { switch (responseType) { case 'arraybuffer': return response.arrayBuffer(); case 'blob': return response.blob(); case 'document': return response.text().then(function (text) { var parser = new DOMParser(); return parser.parseFromString(text, mimeType); }); case 'json': return response.json(); default: if (mimeType === undefined) { return response.text(); } else { // sniff encoding var re = /charset="?([^;"\s]*)"?/i; var exec = re.exec(mimeType); var label = exec && exec[1] ? exec[1].toLowerCase() : undefined; var decoder_1 = new TextDecoder(label); return response.arrayBuffer().then(function (ab) { return decoder_1.decode(ab); }); } } }) .then(function (data) { var callbacks = loading[url]; delete loading[url]; for (var i = 0, il = callbacks.length; i < il; i++) { var callback = callbacks[i]; if (callback.onLoad) callback.onLoad(data); } }) .catch(function (err) { // Abort errors and other errors are handled the same var callbacks = loading[url]; if (callbacks === undefined) { // When onLoad was called and url was deleted in `loading` _this.manager.itemError(url); throw err; } delete loading[url]; for (var i = 0, il = callbacks.length; i < il; i++) { var callback = callbacks[i]; if (callback.onError) callback.onError(err); } _this.manager.itemError(url); }) .finally(function () { _this.manager.itemEnd(url); }); this.manager.itemStart(url); }; /** * Change the response type. Valid values are: * - text or empty string (default) - returns the data as String. * - arraybuffer - loads the data into a ArrayBuffer and returns that. * - blob - returns the data as a Blob. * - document - parses the file using the DOMParser. * - json - parses the file using JSON.parse. * @param value * @returns Return this object */ AcCmFileLoader.prototype.setResponseType = function (value) { this.responseType = value; return this; }; /** * Set the expected mimeType of the file being loaded. Note that in many cases this will be determined * automatically, so by default it is undefined. * @param value The expected mimeType of the file being loaded. * @returns Return this object. */ AcCmFileLoader.prototype.setMimeType = function (value) { this.mimeType = value; return this; }; return AcCmFileLoader; }(AcCmLoader)); export { AcCmFileLoader }; //# sourceMappingURL=AcCmFileLoader.js.map