@mlightcad/common
Version:
[](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@mlightcad/common)
275 lines • 11.2 kB
JavaScript
/**
* @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