UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

300 lines (299 loc) 8.1 kB
import { extend } from "../../core/core.js"; import { now } from "../../core/time.js"; import { path } from "../../core/path.js"; import { URI } from "../../core/uri.js"; import { math } from "../../core/math/math.js"; class Http { static ContentType = { AAC: "audio/aac", BASIS: "image/basis", BIN: "application/octet-stream", DDS: "image/dds", FORM_URLENCODED: "application/x-www-form-urlencoded", GIF: "image/gif", GLB: "model/gltf-binary", JPEG: "image/jpeg", JSON: "application/json", MP3: "audio/mpeg", MP4: "audio/mp4", OGG: "audio/ogg", OPUS: 'audio/ogg; codecs="opus"', PNG: "image/png", TEXT: "text/plain", WAV: "audio/x-wav", XML: "application/xml" }; static ResponseType = { TEXT: "text", ARRAY_BUFFER: "arraybuffer", BLOB: "blob", DOCUMENT: "document", JSON: "json" }; static binaryExtensions = [ ".model", ".wav", ".ogg", ".mp3", ".mp4", ".m4a", ".aac", ".dds", ".basis", ".glb", ".opus" ]; static retryDelay = 100; get(url, options, callback) { if (typeof options === "function") { callback = options; options = {}; } const result = this.request("GET", url, options, callback); const { progress } = options; if (progress) { const handler = (event) => { if (event.lengthComputable) { progress.fire("progress", event.loaded, event.total); } }; const endHandler = (event) => { handler(event); result.removeEventListener("loadstart", handler); result.removeEventListener("progress", handler); result.removeEventListener("loadend", endHandler); }; result.addEventListener("loadstart", handler); result.addEventListener("progress", handler); result.addEventListener("loadend", endHandler); } return result; } post(url, data, options, callback) { if (typeof options === "function") { callback = options; options = {}; } options.postdata = data; return this.request("POST", url, options, callback); } put(url, data, options, callback) { if (typeof options === "function") { callback = options; options = {}; } options.postdata = data; return this.request("PUT", url, options, callback); } del(url, options, callback) { if (typeof options === "function") { callback = options; options = {}; } return this.request("DELETE", url, options, callback); } request(method, url, options, callback) { let uri, query, postdata; let errored = false; if (typeof options === "function") { callback = options; options = {}; } if (options.retry) { options = Object.assign({ retries: 0, maxRetries: 5 }, options); } options.callback = callback; if (options.async == null) { options.async = true; } if (options.headers == null) { options.headers = {}; } if (options.postdata != null) { if (options.postdata instanceof Document) { postdata = options.postdata; } else if (options.postdata instanceof FormData) { postdata = options.postdata; } else if (options.postdata instanceof Object) { let contentType = options.headers["Content-Type"]; if (contentType === void 0) { options.headers["Content-Type"] = Http.ContentType.FORM_URLENCODED; contentType = options.headers["Content-Type"]; } switch (contentType) { case Http.ContentType.FORM_URLENCODED: { postdata = ""; let bFirstItem = true; for (const key in options.postdata) { if (options.postdata.hasOwnProperty(key)) { if (bFirstItem) { bFirstItem = false; } else { postdata += "&"; } const encodedKey = encodeURIComponent(key); const encodedValue = encodeURIComponent(options.postdata[key]); postdata += `${encodedKey}=${encodedValue}`; } } break; } default: case Http.ContentType.JSON: if (contentType == null) { options.headers["Content-Type"] = Http.ContentType.JSON; } postdata = JSON.stringify(options.postdata); break; } } else { postdata = options.postdata; } } if (options.cache === false) { const timestamp = now(); uri = new URI(url); if (!uri.query) { uri.query = `ts=${timestamp}`; } else { uri.query = `${uri.query}&ts=${timestamp}`; } url = uri.toString(); } if (options.query) { uri = new URI(url); query = extend(uri.getQuery(), options.query); uri.setQuery(query); url = uri.toString(); } const xhr = new XMLHttpRequest(); xhr.open(method, url, options.async); xhr.withCredentials = options.withCredentials !== void 0 ? options.withCredentials : false; xhr.responseType = options.responseType || this._guessResponseType(url); for (const header in options.headers) { if (options.headers.hasOwnProperty(header)) { xhr.setRequestHeader(header, options.headers[header]); } } xhr.onreadystatechange = () => { this._onReadyStateChange(method, url, options, xhr); }; xhr.onerror = () => { this._onError(method, url, options, xhr); errored = true; }; try { xhr.send(postdata); } catch (e) { if (!errored) { options.error(xhr.status, xhr, e); } } return xhr; } _guessResponseType(url) { const uri = new URI(url); const ext = path.getExtension(uri.path).toLowerCase(); if (Http.binaryExtensions.indexOf(ext) >= 0) { return Http.ResponseType.ARRAY_BUFFER; } else if (ext === ".json") { return Http.ResponseType.JSON; } else if (ext === ".xml") { return Http.ResponseType.DOCUMENT; } return Http.ResponseType.TEXT; } _isBinaryContentType(contentType) { const binTypes = [ Http.ContentType.BASIS, Http.ContentType.BIN, Http.ContentType.DDS, Http.ContentType.GLB, Http.ContentType.MP3, Http.ContentType.MP4, Http.ContentType.OGG, Http.ContentType.OPUS, Http.ContentType.WAV ]; if (binTypes.indexOf(contentType) >= 0) { return true; } return false; } _isBinaryResponseType(responseType) { return responseType === Http.ResponseType.ARRAY_BUFFER || responseType === Http.ResponseType.BLOB || responseType === Http.ResponseType.JSON; } _onReadyStateChange(method, url, options, xhr) { if (xhr.readyState === 4) { switch (xhr.status) { case 0: { if (xhr.responseURL && xhr.responseURL.startsWith("file:///")) { this._onSuccess(method, url, options, xhr); } else { this._onError(method, url, options, xhr); } break; } case 200: case 201: case 206: case 304: { this._onSuccess(method, url, options, xhr); break; } default: { this._onError(method, url, options, xhr); break; } } } } _onSuccess(method, url, options, xhr) { let response; let contentType; const header = xhr.getResponseHeader("Content-Type"); if (header) { const parts = header.split(";"); contentType = parts[0].trim(); } try { if (this._isBinaryContentType(contentType) || this._isBinaryResponseType(xhr.responseType)) { response = xhr.response; } else if (contentType === Http.ContentType.JSON || url.split("?")[0].endsWith(".json")) { response = JSON.parse(xhr.responseText); } else if (xhr.responseType === Http.ResponseType.DOCUMENT || contentType === Http.ContentType.XML) { response = xhr.responseXML; } else { response = xhr.responseText; } options.callback(null, response); } catch (err) { options.callback(err); } } _onError(method, url, options, xhr) { if (options.retrying) { return; } if (options.retry && options.retries < options.maxRetries) { options.retries++; options.retrying = true; const retryDelay = math.clamp(Math.pow(2, options.retries) * Http.retryDelay, 0, options.maxRetryDelay || 5e3); console.log(`${method}: ${url} - Error ${xhr.status}. Retrying in ${retryDelay} ms`); setTimeout(() => { options.retrying = false; this.request(method, url, options, options.callback); }, retryDelay); } else { options.callback(xhr.status === 0 ? "Network error" : xhr.status, null); } } } const http = new Http(); export { Http, http };