Resource.js 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301
  1. import Uri from "urijs";
  2. import appendForwardSlash from "./appendForwardSlash.js";
  3. import Check from "./Check.js";
  4. import clone from "./clone.js";
  5. import combine from "./combine.js";
  6. import defaultValue from "./defaultValue.js";
  7. import defer from "./defer.js";
  8. import defined from "./defined.js";
  9. import DeveloperError from "./DeveloperError.js";
  10. import getAbsoluteUri from "./getAbsoluteUri.js";
  11. import getBaseUri from "./getBaseUri.js";
  12. import getExtensionFromUri from "./getExtensionFromUri.js";
  13. import getImagePixels from "./getImagePixels.js";
  14. import isBlobUri from "./isBlobUri.js";
  15. import isCrossOriginUrl from "./isCrossOriginUrl.js";
  16. import isDataUri from "./isDataUri.js";
  17. import loadAndExecuteScript from "./loadAndExecuteScript.js";
  18. import CesiumMath from "./Math.js";
  19. import objectToQuery from "./objectToQuery.js";
  20. import queryToObject from "./queryToObject.js";
  21. import Request from "./Request.js";
  22. import RequestErrorEvent from "./RequestErrorEvent.js";
  23. import RequestScheduler from "./RequestScheduler.js";
  24. import RequestState from "./RequestState.js";
  25. import RuntimeError from "./RuntimeError.js";
  26. import TrustedServers from "./TrustedServers.js";
  27. const xhrBlobSupported = (function () {
  28. try {
  29. const xhr = new XMLHttpRequest();
  30. xhr.open("GET", "#", true);
  31. xhr.responseType = "blob";
  32. return xhr.responseType === "blob";
  33. } catch (e) {
  34. return false;
  35. }
  36. })();
  37. /**
  38. * @typedef {object} Resource.ConstructorOptions
  39. *
  40. * Initialization options for the Resource constructor
  41. *
  42. * @property {string} url The url of the resource.
  43. * @property {object} [queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  44. * @property {object} [templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  45. * @property {object} [headers={}] Additional HTTP headers that will be sent.
  46. * @property {Proxy} [proxy] A proxy to be used when loading the resource.
  47. * @property {Resource.RetryCallback} [retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  48. * @property {number} [retryAttempts=0] The number of times the retryCallback should be called before giving up.
  49. * @property {Request} [request] A Request object that will be used. Intended for internal use only.
  50. * @property {boolean} [parseUrl=true] If true, parse the url for query parameters; otherwise store the url without change
  51. */
  52. /**
  53. * A resource that includes the location and any other parameters we need to retrieve it or create derived resources. It also provides the ability to retry requests.
  54. *
  55. * @alias Resource
  56. * @constructor
  57. *
  58. * @param {string|Resource.ConstructorOptions} options A url or an object describing initialization options
  59. *
  60. * @example
  61. * function refreshTokenRetryCallback(resource, error) {
  62. * if (error.statusCode === 403) {
  63. * // 403 status code means a new token should be generated
  64. * return getNewAccessToken()
  65. * .then(function(token) {
  66. * resource.queryParameters.access_token = token;
  67. * return true;
  68. * })
  69. * .catch(function() {
  70. * return false;
  71. * });
  72. * }
  73. *
  74. * return false;
  75. * }
  76. *
  77. * const resource = new Resource({
  78. * url: 'http://server.com/path/to/resource.json',
  79. * proxy: new DefaultProxy('/proxy/'),
  80. * headers: {
  81. * 'X-My-Header': 'valueOfHeader'
  82. * },
  83. * queryParameters: {
  84. * 'access_token': '123-435-456-000'
  85. * },
  86. * retryCallback: refreshTokenRetryCallback,
  87. * retryAttempts: 1
  88. * });
  89. */
  90. function Resource(options) {
  91. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  92. if (typeof options === "string") {
  93. options = {
  94. url: options,
  95. };
  96. }
  97. //>>includeStart('debug', pragmas.debug);
  98. Check.typeOf.string("options.url", options.url);
  99. //>>includeEnd('debug');
  100. this._url = undefined;
  101. this._templateValues = defaultClone(options.templateValues, {});
  102. this._queryParameters = defaultClone(options.queryParameters, {});
  103. /**
  104. * Additional HTTP headers that will be sent with the request.
  105. *
  106. * @type {object}
  107. */
  108. this.headers = defaultClone(options.headers, {});
  109. /**
  110. * A Request object that will be used. Intended for internal use only.
  111. *
  112. * @type {Request}
  113. */
  114. this.request = defaultValue(options.request, new Request());
  115. /**
  116. * A proxy to be used when loading the resource.
  117. *
  118. * @type {Proxy}
  119. */
  120. this.proxy = options.proxy;
  121. /**
  122. * Function to call when a request for this resource fails. If it returns true or a Promise that resolves to true, the request will be retried.
  123. *
  124. * @type {Function}
  125. */
  126. this.retryCallback = options.retryCallback;
  127. /**
  128. * The number of times the retryCallback should be called before giving up.
  129. *
  130. * @type {number}
  131. */
  132. this.retryAttempts = defaultValue(options.retryAttempts, 0);
  133. this._retryCount = 0;
  134. const parseUrl = defaultValue(options.parseUrl, true);
  135. if (parseUrl) {
  136. this.parseUrl(options.url, true, true);
  137. } else {
  138. this._url = options.url;
  139. }
  140. }
  141. /**
  142. * Clones a value if it is defined, otherwise returns the default value
  143. *
  144. * @param {object} [value] The value to clone.
  145. * @param {object} [defaultValue] The default value.
  146. *
  147. * @returns {object} A clone of value or the defaultValue.
  148. *
  149. * @private
  150. */
  151. function defaultClone(value, defaultValue) {
  152. return defined(value) ? clone(value) : defaultValue;
  153. }
  154. /**
  155. * A helper function to create a resource depending on whether we have a String or a Resource
  156. *
  157. * @param {Resource|string} resource A Resource or a String to use when creating a new Resource.
  158. *
  159. * @returns {Resource} If resource is a String, a Resource constructed with the url and options. Otherwise the resource parameter is returned.
  160. *
  161. * @private
  162. */
  163. Resource.createIfNeeded = function (resource) {
  164. if (resource instanceof Resource) {
  165. // Keep existing request object. This function is used internally to duplicate a Resource, so that it can't
  166. // be modified outside of a class that holds it (eg. an imagery or terrain provider). Since the Request objects
  167. // are managed outside of the providers, by the tile loading code, we want to keep the request property the same so if it is changed
  168. // in the underlying tiling code the requests for this resource will use it.
  169. return resource.getDerivedResource({
  170. request: resource.request,
  171. });
  172. }
  173. if (typeof resource !== "string") {
  174. return resource;
  175. }
  176. return new Resource({
  177. url: resource,
  178. });
  179. };
  180. let supportsImageBitmapOptionsPromise;
  181. /**
  182. * A helper function to check whether createImageBitmap supports passing ImageBitmapOptions.
  183. *
  184. * @returns {Promise<boolean>} A promise that resolves to true if this browser supports creating an ImageBitmap with options.
  185. *
  186. * @private
  187. */
  188. Resource.supportsImageBitmapOptions = function () {
  189. // Until the HTML folks figure out what to do about this, we need to actually try loading an image to
  190. // know if this browser supports passing options to the createImageBitmap function.
  191. // https://github.com/whatwg/html/pull/4248
  192. //
  193. // We also need to check whether the colorSpaceConversion option is supported.
  194. // We do this by loading a PNG with an embedded color profile, first with
  195. // colorSpaceConversion: "none" and then with colorSpaceConversion: "default".
  196. // If the pixel color is different then we know the option is working.
  197. // As of Webkit 17612.3.6.1.6 the createImageBitmap promise resolves but the
  198. // option is not actually supported.
  199. if (defined(supportsImageBitmapOptionsPromise)) {
  200. return supportsImageBitmapOptionsPromise;
  201. }
  202. if (typeof createImageBitmap !== "function") {
  203. supportsImageBitmapOptionsPromise = Promise.resolve(false);
  204. return supportsImageBitmapOptionsPromise;
  205. }
  206. const imageDataUri =
  207. "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAABGdBTUEAAE4g3rEiDgAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADElEQVQI12Ng6GAAAAEUAIngE3ZiAAAAAElFTkSuQmCC";
  208. supportsImageBitmapOptionsPromise = Resource.fetchBlob({
  209. url: imageDataUri,
  210. })
  211. .then(function (blob) {
  212. const imageBitmapOptions = {
  213. imageOrientation: "flipY", // default is "none"
  214. premultiplyAlpha: "none", // default is "default"
  215. colorSpaceConversion: "none", // default is "default"
  216. };
  217. return Promise.all([
  218. createImageBitmap(blob, imageBitmapOptions),
  219. createImageBitmap(blob),
  220. ]);
  221. })
  222. .then(function (imageBitmaps) {
  223. // Check whether the colorSpaceConversion option had any effect on the green channel
  224. const colorWithOptions = getImagePixels(imageBitmaps[0]);
  225. const colorWithDefaults = getImagePixels(imageBitmaps[1]);
  226. return colorWithOptions[1] !== colorWithDefaults[1];
  227. })
  228. .catch(function () {
  229. return false;
  230. });
  231. return supportsImageBitmapOptionsPromise;
  232. };
  233. Object.defineProperties(Resource, {
  234. /**
  235. * Returns true if blobs are supported.
  236. *
  237. * @memberof Resource
  238. * @type {boolean}
  239. *
  240. * @readonly
  241. */
  242. isBlobSupported: {
  243. get: function () {
  244. return xhrBlobSupported;
  245. },
  246. },
  247. });
  248. Object.defineProperties(Resource.prototype, {
  249. /**
  250. * Query parameters appended to the url.
  251. *
  252. * @memberof Resource.prototype
  253. * @type {object}
  254. *
  255. * @readonly
  256. */
  257. queryParameters: {
  258. get: function () {
  259. return this._queryParameters;
  260. },
  261. },
  262. /**
  263. * The key/value pairs used to replace template parameters in the url.
  264. *
  265. * @memberof Resource.prototype
  266. * @type {object}
  267. *
  268. * @readonly
  269. */
  270. templateValues: {
  271. get: function () {
  272. return this._templateValues;
  273. },
  274. },
  275. /**
  276. * The url to the resource with template values replaced, query string appended and encoded by proxy if one was set.
  277. *
  278. * @memberof Resource.prototype
  279. * @type {string}
  280. */
  281. url: {
  282. get: function () {
  283. return this.getUrlComponent(true, true);
  284. },
  285. set: function (value) {
  286. this.parseUrl(value, false, false);
  287. },
  288. },
  289. /**
  290. * The file extension of the resource.
  291. *
  292. * @memberof Resource.prototype
  293. * @type {string}
  294. *
  295. * @readonly
  296. */
  297. extension: {
  298. get: function () {
  299. return getExtensionFromUri(this._url);
  300. },
  301. },
  302. /**
  303. * True if the Resource refers to a data URI.
  304. *
  305. * @memberof Resource.prototype
  306. * @type {boolean}
  307. */
  308. isDataUri: {
  309. get: function () {
  310. return isDataUri(this._url);
  311. },
  312. },
  313. /**
  314. * True if the Resource refers to a blob URI.
  315. *
  316. * @memberof Resource.prototype
  317. * @type {boolean}
  318. */
  319. isBlobUri: {
  320. get: function () {
  321. return isBlobUri(this._url);
  322. },
  323. },
  324. /**
  325. * True if the Resource refers to a cross origin URL.
  326. *
  327. * @memberof Resource.prototype
  328. * @type {boolean}
  329. */
  330. isCrossOriginUrl: {
  331. get: function () {
  332. return isCrossOriginUrl(this._url);
  333. },
  334. },
  335. /**
  336. * True if the Resource has request headers. This is equivalent to checking if the headers property has any keys.
  337. *
  338. * @memberof Resource.prototype
  339. * @type {boolean}
  340. */
  341. hasHeaders: {
  342. get: function () {
  343. return Object.keys(this.headers).length > 0;
  344. },
  345. },
  346. });
  347. /**
  348. * Override Object#toString so that implicit string conversion gives the
  349. * complete URL represented by this Resource.
  350. *
  351. * @returns {string} The URL represented by this Resource
  352. */
  353. Resource.prototype.toString = function () {
  354. return this.getUrlComponent(true, true);
  355. };
  356. /**
  357. * Parse a url string, and store its info
  358. *
  359. * @param {string} url The input url string.
  360. * @param {boolean} merge If true, we'll merge with the resource's existing queryParameters. Otherwise they will be replaced.
  361. * @param {boolean} preserveQuery If true duplicate parameters will be concatenated into an array. If false, keys in url will take precedence.
  362. * @param {string} [baseUrl] If supplied, and input url is a relative url, it will be made absolute relative to baseUrl
  363. *
  364. * @private
  365. */
  366. Resource.prototype.parseUrl = function (url, merge, preserveQuery, baseUrl) {
  367. let uri = new Uri(url);
  368. const query = parseQueryString(uri.query());
  369. this._queryParameters = merge
  370. ? combineQueryParameters(query, this.queryParameters, preserveQuery)
  371. : query;
  372. // Remove unneeded info from the Uri
  373. uri.search("");
  374. uri.fragment("");
  375. if (defined(baseUrl) && uri.scheme() === "") {
  376. uri = uri.absoluteTo(getAbsoluteUri(baseUrl));
  377. }
  378. this._url = uri.toString();
  379. };
  380. /**
  381. * Parses a query string and returns the object equivalent.
  382. *
  383. * @param {string} queryString The query string
  384. * @returns {object}
  385. *
  386. * @private
  387. */
  388. function parseQueryString(queryString) {
  389. if (queryString.length === 0) {
  390. return {};
  391. }
  392. // Special case where the querystring is just a string, not key/value pairs
  393. if (queryString.indexOf("=") === -1) {
  394. return { [queryString]: undefined };
  395. }
  396. return queryToObject(queryString);
  397. }
  398. /**
  399. * This combines a map of query parameters.
  400. *
  401. * @param {object} q1 The first map of query parameters. Values in this map will take precedence if preserveQueryParameters is false.
  402. * @param {object} q2 The second map of query parameters.
  403. * @param {boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in q1 will take precedence.
  404. *
  405. * @returns {object} The combined map of query parameters.
  406. *
  407. * @example
  408. * const q1 = {
  409. * a: 1,
  410. * b: 2
  411. * };
  412. * const q2 = {
  413. * a: 3,
  414. * c: 4
  415. * };
  416. * const q3 = {
  417. * b: [5, 6],
  418. * d: 7
  419. * }
  420. *
  421. * // Returns
  422. * // {
  423. * // a: [1, 3],
  424. * // b: 2,
  425. * // c: 4
  426. * // };
  427. * combineQueryParameters(q1, q2, true);
  428. *
  429. * // Returns
  430. * // {
  431. * // a: 1,
  432. * // b: 2,
  433. * // c: 4
  434. * // };
  435. * combineQueryParameters(q1, q2, false);
  436. *
  437. * // Returns
  438. * // {
  439. * // a: 1,
  440. * // b: [2, 5, 6],
  441. * // d: 7
  442. * // };
  443. * combineQueryParameters(q1, q3, true);
  444. *
  445. * // Returns
  446. * // {
  447. * // a: 1,
  448. * // b: 2,
  449. * // d: 7
  450. * // };
  451. * combineQueryParameters(q1, q3, false);
  452. *
  453. * @private
  454. */
  455. function combineQueryParameters(q1, q2, preserveQueryParameters) {
  456. if (!preserveQueryParameters) {
  457. return combine(q1, q2);
  458. }
  459. const result = clone(q1, true);
  460. for (const param in q2) {
  461. if (q2.hasOwnProperty(param)) {
  462. let value = result[param];
  463. const q2Value = q2[param];
  464. if (defined(value)) {
  465. if (!Array.isArray(value)) {
  466. value = result[param] = [value];
  467. }
  468. result[param] = value.concat(q2Value);
  469. } else {
  470. result[param] = Array.isArray(q2Value) ? q2Value.slice() : q2Value;
  471. }
  472. }
  473. }
  474. return result;
  475. }
  476. /**
  477. * Returns the url, optional with the query string and processed by a proxy.
  478. *
  479. * @param {boolean} [query=false] If true, the query string is included.
  480. * @param {boolean} [proxy=false] If true, the url is processed by the proxy object, if defined.
  481. *
  482. * @returns {string} The url with all the requested components.
  483. */
  484. Resource.prototype.getUrlComponent = function (query, proxy) {
  485. if (this.isDataUri) {
  486. return this._url;
  487. }
  488. let url = this._url;
  489. if (query) {
  490. url = `${url}${stringifyQuery(this.queryParameters)}`;
  491. }
  492. // Restore the placeholders, which may have been escaped in objectToQuery or elsewhere
  493. url = url.replace(/%7B/g, "{").replace(/%7D/g, "}");
  494. const templateValues = this._templateValues;
  495. if (Object.keys(templateValues).length > 0) {
  496. url = url.replace(/{(.*?)}/g, function (match, key) {
  497. const replacement = templateValues[key];
  498. if (defined(replacement)) {
  499. // use the replacement value from templateValues if there is one...
  500. return encodeURIComponent(replacement);
  501. }
  502. // otherwise leave it unchanged
  503. return match;
  504. });
  505. }
  506. if (proxy && defined(this.proxy)) {
  507. url = this.proxy.getURL(url);
  508. }
  509. return url;
  510. };
  511. /**
  512. * Converts a query object into a string.
  513. *
  514. * @param {object} queryObject The object with query parameters
  515. * @returns {string}
  516. *
  517. * @private
  518. */
  519. function stringifyQuery(queryObject) {
  520. const keys = Object.keys(queryObject);
  521. if (keys.length === 0) {
  522. return "";
  523. }
  524. if (keys.length === 1 && !defined(queryObject[keys[0]])) {
  525. // We have 1 key with an undefined value, so this is just a string, not key/value pairs
  526. return `?${keys[0]}`;
  527. }
  528. return `?${objectToQuery(queryObject)}`;
  529. }
  530. /**
  531. * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
  532. * as opposed to adding them one at a time to the queryParameters property. If a value is already set, it will be replaced with the new value.
  533. *
  534. * @param {object} params The query parameters
  535. * @param {boolean} [useAsDefault=false] If true the params will be used as the default values, so they will only be set if they are undefined.
  536. */
  537. Resource.prototype.setQueryParameters = function (params, useAsDefault) {
  538. if (useAsDefault) {
  539. this._queryParameters = combineQueryParameters(
  540. this._queryParameters,
  541. params,
  542. false
  543. );
  544. } else {
  545. this._queryParameters = combineQueryParameters(
  546. params,
  547. this._queryParameters,
  548. false
  549. );
  550. }
  551. };
  552. /**
  553. * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
  554. * as opposed to adding them one at a time to the queryParameters property.
  555. *
  556. * @param {object} params The query parameters
  557. */
  558. Resource.prototype.appendQueryParameters = function (params) {
  559. this._queryParameters = combineQueryParameters(
  560. params,
  561. this._queryParameters,
  562. true
  563. );
  564. };
  565. /**
  566. * Combines the specified object and the existing template values. This allows you to add many values at once,
  567. * as opposed to adding them one at a time to the templateValues property. If a value is already set, it will become an array and the new value will be appended.
  568. *
  569. * @param {object} template The template values
  570. * @param {boolean} [useAsDefault=false] If true the values will be used as the default values, so they will only be set if they are undefined.
  571. */
  572. Resource.prototype.setTemplateValues = function (template, useAsDefault) {
  573. if (useAsDefault) {
  574. this._templateValues = combine(this._templateValues, template);
  575. } else {
  576. this._templateValues = combine(template, this._templateValues);
  577. }
  578. };
  579. /**
  580. * Returns a resource relative to the current instance. All properties remain the same as the current instance unless overridden in options.
  581. *
  582. * @param {object} options An object with the following properties
  583. * @param {string} [options.url] The url that will be resolved relative to the url of the current instance.
  584. * @param {object} [options.queryParameters] An object containing query parameters that will be combined with those of the current instance.
  585. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). These will be combined with those of the current instance.
  586. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  587. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  588. * @param {Resource.RetryCallback} [options.retryCallback] The function to call when loading the resource fails.
  589. * @param {number} [options.retryAttempts] The number of times the retryCallback should be called before giving up.
  590. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  591. * @param {boolean} [options.preserveQueryParameters=false] If true, this will keep all query parameters from the current resource and derived resource. If false, derived parameters will replace those of the current resource.
  592. *
  593. * @returns {Resource} The resource derived from the current one.
  594. */
  595. Resource.prototype.getDerivedResource = function (options) {
  596. const resource = this.clone();
  597. resource._retryCount = 0;
  598. if (defined(options.url)) {
  599. const preserveQuery = defaultValue(options.preserveQueryParameters, false);
  600. resource.parseUrl(options.url, true, preserveQuery, this._url);
  601. }
  602. if (defined(options.queryParameters)) {
  603. resource._queryParameters = combine(
  604. options.queryParameters,
  605. resource.queryParameters
  606. );
  607. }
  608. if (defined(options.templateValues)) {
  609. resource._templateValues = combine(
  610. options.templateValues,
  611. resource.templateValues
  612. );
  613. }
  614. if (defined(options.headers)) {
  615. resource.headers = combine(options.headers, resource.headers);
  616. }
  617. if (defined(options.proxy)) {
  618. resource.proxy = options.proxy;
  619. }
  620. if (defined(options.request)) {
  621. resource.request = options.request;
  622. }
  623. if (defined(options.retryCallback)) {
  624. resource.retryCallback = options.retryCallback;
  625. }
  626. if (defined(options.retryAttempts)) {
  627. resource.retryAttempts = options.retryAttempts;
  628. }
  629. return resource;
  630. };
  631. /**
  632. * Called when a resource fails to load. This will call the retryCallback function if defined until retryAttempts is reached.
  633. *
  634. * @param {RequestErrorEvent} [error] The error that was encountered.
  635. *
  636. * @returns {Promise<boolean>} A promise to a boolean, that if true will cause the resource request to be retried.
  637. *
  638. * @private
  639. */
  640. Resource.prototype.retryOnError = function (error) {
  641. const retryCallback = this.retryCallback;
  642. if (
  643. typeof retryCallback !== "function" ||
  644. this._retryCount >= this.retryAttempts
  645. ) {
  646. return Promise.resolve(false);
  647. }
  648. const that = this;
  649. return Promise.resolve(retryCallback(this, error)).then(function (result) {
  650. ++that._retryCount;
  651. return result;
  652. });
  653. };
  654. /**
  655. * Duplicates a Resource instance.
  656. *
  657. * @param {Resource} [result] The object onto which to store the result.
  658. *
  659. * @returns {Resource} The modified result parameter or a new Resource instance if one was not provided.
  660. */
  661. Resource.prototype.clone = function (result) {
  662. if (!defined(result)) {
  663. return new Resource({
  664. url: this._url,
  665. queryParameters: this.queryParameters,
  666. templateValues: this.templateValues,
  667. headers: this.headers,
  668. proxy: this.proxy,
  669. retryCallback: this.retryCallback,
  670. retryAttempts: this.retryAttempts,
  671. request: this.request.clone(),
  672. parseUrl: false,
  673. });
  674. }
  675. result._url = this._url;
  676. result._queryParameters = clone(this._queryParameters);
  677. result._templateValues = clone(this._templateValues);
  678. result.headers = clone(this.headers);
  679. result.proxy = this.proxy;
  680. result.retryCallback = this.retryCallback;
  681. result.retryAttempts = this.retryAttempts;
  682. result._retryCount = 0;
  683. result.request = this.request.clone();
  684. return result;
  685. };
  686. /**
  687. * Returns the base path of the Resource.
  688. *
  689. * @param {boolean} [includeQuery = false] Whether or not to include the query string and fragment form the uri
  690. *
  691. * @returns {string} The base URI of the resource
  692. */
  693. Resource.prototype.getBaseUri = function (includeQuery) {
  694. return getBaseUri(this.getUrlComponent(includeQuery), includeQuery);
  695. };
  696. /**
  697. * Appends a forward slash to the URL.
  698. */
  699. Resource.prototype.appendForwardSlash = function () {
  700. this._url = appendForwardSlash(this._url);
  701. };
  702. /**
  703. * Asynchronously loads the resource as raw binary data. Returns a promise that will resolve to
  704. * an ArrayBuffer once loaded, or reject if the resource failed to load. The data is loaded
  705. * using XMLHttpRequest, which means that in order to make requests to another origin,
  706. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  707. *
  708. * @returns {Promise<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  709. *
  710. * @example
  711. * // load a single URL asynchronously
  712. * resource.fetchArrayBuffer().then(function(arrayBuffer) {
  713. * // use the data
  714. * }).catch(function(error) {
  715. * // an error occurred
  716. * });
  717. *
  718. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  719. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  720. */
  721. Resource.prototype.fetchArrayBuffer = function () {
  722. return this.fetch({
  723. responseType: "arraybuffer",
  724. });
  725. };
  726. /**
  727. * Creates a Resource and calls fetchArrayBuffer() on it.
  728. *
  729. * @param {string|object} options A url or an object with the following properties
  730. * @param {string} options.url The url of the resource.
  731. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  732. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  733. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  734. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  735. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  736. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  737. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  738. * @returns {Promise<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  739. */
  740. Resource.fetchArrayBuffer = function (options) {
  741. const resource = new Resource(options);
  742. return resource.fetchArrayBuffer();
  743. };
  744. /**
  745. * Asynchronously loads the given resource as a blob. Returns a promise that will resolve to
  746. * a Blob once loaded, or reject if the resource failed to load. The data is loaded
  747. * using XMLHttpRequest, which means that in order to make requests to another origin,
  748. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  749. *
  750. * @returns {Promise<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  751. *
  752. * @example
  753. * // load a single URL asynchronously
  754. * resource.fetchBlob().then(function(blob) {
  755. * // use the data
  756. * }).catch(function(error) {
  757. * // an error occurred
  758. * });
  759. *
  760. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  761. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  762. */
  763. Resource.prototype.fetchBlob = function () {
  764. return this.fetch({
  765. responseType: "blob",
  766. });
  767. };
  768. /**
  769. * Creates a Resource and calls fetchBlob() on it.
  770. *
  771. * @param {string|object} options A url or an object with the following properties
  772. * @param {string} options.url The url of the resource.
  773. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  774. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  775. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  776. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  777. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  778. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  779. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  780. * @returns {Promise<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  781. */
  782. Resource.fetchBlob = function (options) {
  783. const resource = new Resource(options);
  784. return resource.fetchBlob();
  785. };
  786. /**
  787. * Asynchronously loads the given image resource. Returns a promise that will resolve to
  788. * an {@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap|ImageBitmap} if <code>preferImageBitmap</code> is true and the browser supports <code>createImageBitmap</code> or otherwise an
  789. * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement|Image} once loaded, or reject if the image failed to load.
  790. *
  791. * @param {object} [options] An object with the following properties.
  792. * @param {boolean} [options.preferBlob=false] If true, we will load the image via a blob.
  793. * @param {boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  794. * @param {boolean} [options.flipY=false] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>.
  795. * @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the image will be ignored. Only applies if the browser supports <code>createImageBitmap</code>.
  796. * @returns {Promise<ImageBitmap|HTMLImageElement>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  797. *
  798. *
  799. * @example
  800. * // load a single image asynchronously
  801. * resource.fetchImage().then(function(image) {
  802. * // use the loaded image
  803. * }).catch(function(error) {
  804. * // an error occurred
  805. * });
  806. *
  807. * // load several images in parallel
  808. * Promise.all([resource1.fetchImage(), resource2.fetchImage()]).then(function(images) {
  809. * // images is an array containing all the loaded images
  810. * });
  811. *
  812. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  813. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  814. */
  815. Resource.prototype.fetchImage = function (options) {
  816. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  817. const preferImageBitmap = defaultValue(options.preferImageBitmap, false);
  818. const preferBlob = defaultValue(options.preferBlob, false);
  819. const flipY = defaultValue(options.flipY, false);
  820. const skipColorSpaceConversion = defaultValue(
  821. options.skipColorSpaceConversion,
  822. false
  823. );
  824. checkAndResetRequest(this.request);
  825. // We try to load the image normally if
  826. // 1. Blobs aren't supported
  827. // 2. It's a data URI
  828. // 3. It's a blob URI
  829. // 4. It doesn't have request headers and we preferBlob is false
  830. if (
  831. !xhrBlobSupported ||
  832. this.isDataUri ||
  833. this.isBlobUri ||
  834. (!this.hasHeaders && !preferBlob)
  835. ) {
  836. return fetchImage({
  837. resource: this,
  838. flipY: flipY,
  839. skipColorSpaceConversion: skipColorSpaceConversion,
  840. preferImageBitmap: preferImageBitmap,
  841. });
  842. }
  843. const blobPromise = this.fetchBlob();
  844. if (!defined(blobPromise)) {
  845. return;
  846. }
  847. let supportsImageBitmap;
  848. let useImageBitmap;
  849. let generatedBlobResource;
  850. let generatedBlob;
  851. return Resource.supportsImageBitmapOptions()
  852. .then(function (result) {
  853. supportsImageBitmap = result;
  854. useImageBitmap = supportsImageBitmap && preferImageBitmap;
  855. return blobPromise;
  856. })
  857. .then(function (blob) {
  858. if (!defined(blob)) {
  859. return;
  860. }
  861. generatedBlob = blob;
  862. if (useImageBitmap) {
  863. return Resource.createImageBitmapFromBlob(blob, {
  864. flipY: flipY,
  865. premultiplyAlpha: false,
  866. skipColorSpaceConversion: skipColorSpaceConversion,
  867. });
  868. }
  869. const blobUrl = window.URL.createObjectURL(blob);
  870. generatedBlobResource = new Resource({
  871. url: blobUrl,
  872. });
  873. return fetchImage({
  874. resource: generatedBlobResource,
  875. flipY: flipY,
  876. skipColorSpaceConversion: skipColorSpaceConversion,
  877. preferImageBitmap: false,
  878. });
  879. })
  880. .then(function (image) {
  881. if (!defined(image)) {
  882. return;
  883. }
  884. // The blob object may be needed for use by a TileDiscardPolicy,
  885. // so attach it to the image.
  886. image.blob = generatedBlob;
  887. if (useImageBitmap) {
  888. return image;
  889. }
  890. window.URL.revokeObjectURL(generatedBlobResource.url);
  891. return image;
  892. })
  893. .catch(function (error) {
  894. if (defined(generatedBlobResource)) {
  895. window.URL.revokeObjectURL(generatedBlobResource.url);
  896. }
  897. // If the blob load succeeded but the image decode failed, attach the blob
  898. // to the error object for use by a TileDiscardPolicy.
  899. // In particular, BingMapsImageryProvider uses this to detect the
  900. // zero-length response that is returned when a tile is not available.
  901. error.blob = generatedBlob;
  902. return Promise.reject(error);
  903. });
  904. };
  905. /**
  906. * Fetches an image and returns a promise to it.
  907. *
  908. * @param {object} [options] An object with the following properties.
  909. * @param {Resource} [options.resource] Resource object that points to an image to fetch.
  910. * @param {boolean} [options.preferImageBitmap] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  911. * @param {boolean} [options.flipY] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>.
  912. * @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the image will be ignored. Only applies if the browser supports <code>createImageBitmap</code>.
  913. * @private
  914. */
  915. function fetchImage(options) {
  916. const resource = options.resource;
  917. const flipY = options.flipY;
  918. const skipColorSpaceConversion = options.skipColorSpaceConversion;
  919. const preferImageBitmap = options.preferImageBitmap;
  920. const request = resource.request;
  921. request.url = resource.url;
  922. request.requestFunction = function () {
  923. let crossOrigin = false;
  924. // data URIs can't have crossorigin set.
  925. if (!resource.isDataUri && !resource.isBlobUri) {
  926. crossOrigin = resource.isCrossOriginUrl;
  927. }
  928. const deferred = defer();
  929. Resource._Implementations.createImage(
  930. request,
  931. crossOrigin,
  932. deferred,
  933. flipY,
  934. skipColorSpaceConversion,
  935. preferImageBitmap
  936. );
  937. return deferred.promise;
  938. };
  939. const promise = RequestScheduler.request(request);
  940. if (!defined(promise)) {
  941. return;
  942. }
  943. return promise.catch(function (e) {
  944. // Don't retry cancelled or otherwise aborted requests
  945. if (request.state !== RequestState.FAILED) {
  946. return Promise.reject(e);
  947. }
  948. return resource.retryOnError(e).then(function (retry) {
  949. if (retry) {
  950. // Reset request so it can try again
  951. request.state = RequestState.UNISSUED;
  952. request.deferred = undefined;
  953. return fetchImage({
  954. resource: resource,
  955. flipY: flipY,
  956. skipColorSpaceConversion: skipColorSpaceConversion,
  957. preferImageBitmap: preferImageBitmap,
  958. });
  959. }
  960. return Promise.reject(e);
  961. });
  962. });
  963. }
  964. /**
  965. * Creates a Resource and calls fetchImage() on it.
  966. *
  967. * @param {string|object} options A url or an object with the following properties
  968. * @param {string} options.url The url of the resource.
  969. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  970. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  971. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  972. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  973. * @param {boolean} [options.flipY=false] Whether to vertically flip the image during fetch and decode. Only applies when requesting an image and the browser supports <code>createImageBitmap</code>.
  974. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  975. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  976. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  977. * @param {boolean} [options.preferBlob=false] If true, we will load the image via a blob.
  978. * @param {boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  979. * @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the image will be ignored. Only applies when requesting an image and the browser supports <code>createImageBitmap</code>.
  980. * @returns {Promise<ImageBitmap|HTMLImageElement>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  981. */
  982. Resource.fetchImage = function (options) {
  983. const resource = new Resource(options);
  984. return resource.fetchImage({
  985. flipY: options.flipY,
  986. skipColorSpaceConversion: options.skipColorSpaceConversion,
  987. preferBlob: options.preferBlob,
  988. preferImageBitmap: options.preferImageBitmap,
  989. });
  990. };
  991. /**
  992. * Asynchronously loads the given resource as text. Returns a promise that will resolve to
  993. * a String once loaded, or reject if the resource failed to load. The data is loaded
  994. * using XMLHttpRequest, which means that in order to make requests to another origin,
  995. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  996. *
  997. * @returns {Promise<string>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  998. *
  999. * @example
  1000. * // load text from a URL, setting a custom header
  1001. * const resource = new Resource({
  1002. * url: 'http://someUrl.com/someJson.txt',
  1003. * headers: {
  1004. * 'X-Custom-Header' : 'some value'
  1005. * }
  1006. * });
  1007. * resource.fetchText().then(function(text) {
  1008. * // Do something with the text
  1009. * }).catch(function(error) {
  1010. * // an error occurred
  1011. * });
  1012. *
  1013. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
  1014. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1015. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1016. */
  1017. Resource.prototype.fetchText = function () {
  1018. return this.fetch({
  1019. responseType: "text",
  1020. });
  1021. };
  1022. /**
  1023. * Creates a Resource and calls fetchText() on it.
  1024. *
  1025. * @param {string|object} options A url or an object with the following properties
  1026. * @param {string} options.url The url of the resource.
  1027. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1028. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1029. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1030. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1031. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1032. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1033. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1034. * @returns {Promise<string>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1035. */
  1036. Resource.fetchText = function (options) {
  1037. const resource = new Resource(options);
  1038. return resource.fetchText();
  1039. };
  1040. // note: &#42;&#47;&#42; below is */* but that ends the comment block early
  1041. /**
  1042. * Asynchronously loads the given resource as JSON. Returns a promise that will resolve to
  1043. * a JSON object once loaded, or reject if the resource failed to load. The data is loaded
  1044. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1045. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. This function
  1046. * adds 'Accept: application/json,&#42;&#47;&#42;;q=0.01' to the request headers, if not
  1047. * already specified.
  1048. *
  1049. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1050. *
  1051. *
  1052. * @example
  1053. * resource.fetchJson().then(function(jsonData) {
  1054. * // Do something with the JSON object
  1055. * }).catch(function(error) {
  1056. * // an error occurred
  1057. * });
  1058. *
  1059. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1060. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1061. */
  1062. Resource.prototype.fetchJson = function () {
  1063. const promise = this.fetch({
  1064. responseType: "text",
  1065. headers: {
  1066. Accept: "application/json,*/*;q=0.01",
  1067. },
  1068. });
  1069. if (!defined(promise)) {
  1070. return undefined;
  1071. }
  1072. return promise.then(function (value) {
  1073. if (!defined(value)) {
  1074. return;
  1075. }
  1076. return JSON.parse(value);
  1077. });
  1078. };
  1079. /**
  1080. * Creates a Resource and calls fetchJson() on it.
  1081. *
  1082. * @param {string|object} options A url or an object with the following properties
  1083. * @param {string} options.url The url of the resource.
  1084. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1085. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1086. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1087. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1088. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1089. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1090. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1091. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1092. */
  1093. Resource.fetchJson = function (options) {
  1094. const resource = new Resource(options);
  1095. return resource.fetchJson();
  1096. };
  1097. /**
  1098. * Asynchronously loads the given resource as XML. Returns a promise that will resolve to
  1099. * an XML Document once loaded, or reject if the resource failed to load. The data is loaded
  1100. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1101. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1102. *
  1103. * @returns {Promise<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1104. *
  1105. *
  1106. * @example
  1107. * // load XML from a URL, setting a custom header
  1108. * Cesium.loadXML('http://someUrl.com/someXML.xml', {
  1109. * 'X-Custom-Header' : 'some value'
  1110. * }).then(function(document) {
  1111. * // Do something with the document
  1112. * }).catch(function(error) {
  1113. * // an error occurred
  1114. * });
  1115. *
  1116. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
  1117. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1118. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1119. */
  1120. Resource.prototype.fetchXML = function () {
  1121. return this.fetch({
  1122. responseType: "document",
  1123. overrideMimeType: "text/xml",
  1124. });
  1125. };
  1126. /**
  1127. * Creates a Resource and calls fetchXML() on it.
  1128. *
  1129. * @param {string|object} options A url or an object with the following properties
  1130. * @param {string} options.url The url of the resource.
  1131. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1132. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1133. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1134. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1135. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1136. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1137. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1138. * @returns {Promise<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1139. */
  1140. Resource.fetchXML = function (options) {
  1141. const resource = new Resource(options);
  1142. return resource.fetchXML();
  1143. };
  1144. /**
  1145. * Requests a resource using JSONP.
  1146. *
  1147. * @param {string} [callbackParameterName='callback'] The callback parameter name that the server expects.
  1148. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1149. *
  1150. *
  1151. * @example
  1152. * // load a data asynchronously
  1153. * resource.fetchJsonp().then(function(data) {
  1154. * // use the loaded data
  1155. * }).catch(function(error) {
  1156. * // an error occurred
  1157. * });
  1158. *
  1159. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1160. */
  1161. Resource.prototype.fetchJsonp = function (callbackParameterName) {
  1162. callbackParameterName = defaultValue(callbackParameterName, "callback");
  1163. checkAndResetRequest(this.request);
  1164. //generate a unique function name
  1165. let functionName;
  1166. do {
  1167. functionName = `loadJsonp${CesiumMath.nextRandomNumber()
  1168. .toString()
  1169. .substring(2, 8)}`;
  1170. } while (defined(window[functionName]));
  1171. return fetchJsonp(this, callbackParameterName, functionName);
  1172. };
  1173. function fetchJsonp(resource, callbackParameterName, functionName) {
  1174. const callbackQuery = {};
  1175. callbackQuery[callbackParameterName] = functionName;
  1176. resource.setQueryParameters(callbackQuery);
  1177. const request = resource.request;
  1178. const url = resource.url;
  1179. request.url = url;
  1180. request.requestFunction = function () {
  1181. const deferred = defer();
  1182. //assign a function with that name in the global scope
  1183. window[functionName] = function (data) {
  1184. deferred.resolve(data);
  1185. try {
  1186. delete window[functionName];
  1187. } catch (e) {
  1188. window[functionName] = undefined;
  1189. }
  1190. };
  1191. Resource._Implementations.loadAndExecuteScript(url, functionName, deferred);
  1192. return deferred.promise;
  1193. };
  1194. const promise = RequestScheduler.request(request);
  1195. if (!defined(promise)) {
  1196. return;
  1197. }
  1198. return promise.catch(function (e) {
  1199. if (request.state !== RequestState.FAILED) {
  1200. return Promise.reject(e);
  1201. }
  1202. return resource.retryOnError(e).then(function (retry) {
  1203. if (retry) {
  1204. // Reset request so it can try again
  1205. request.state = RequestState.UNISSUED;
  1206. request.deferred = undefined;
  1207. return fetchJsonp(resource, callbackParameterName, functionName);
  1208. }
  1209. return Promise.reject(e);
  1210. });
  1211. });
  1212. }
  1213. /**
  1214. * Creates a Resource from a URL and calls fetchJsonp() on it.
  1215. *
  1216. * @param {string|object} options A url or an object with the following properties
  1217. * @param {string} options.url The url of the resource.
  1218. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1219. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1220. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1221. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1222. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1223. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1224. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1225. * @param {string} [options.callbackParameterName='callback'] The callback parameter name that the server expects.
  1226. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1227. */
  1228. Resource.fetchJsonp = function (options) {
  1229. const resource = new Resource(options);
  1230. return resource.fetchJsonp(options.callbackParameterName);
  1231. };
  1232. /**
  1233. * @private
  1234. */
  1235. Resource.prototype._makeRequest = function (options) {
  1236. const resource = this;
  1237. checkAndResetRequest(resource.request);
  1238. const request = resource.request;
  1239. const url = resource.url;
  1240. request.url = url;
  1241. request.requestFunction = function () {
  1242. const responseType = options.responseType;
  1243. const headers = combine(options.headers, resource.headers);
  1244. const overrideMimeType = options.overrideMimeType;
  1245. const method = options.method;
  1246. const data = options.data;
  1247. const deferred = defer();
  1248. const xhr = Resource._Implementations.loadWithXhr(
  1249. url,
  1250. responseType,
  1251. method,
  1252. data,
  1253. headers,
  1254. deferred,
  1255. overrideMimeType
  1256. );
  1257. if (defined(xhr) && defined(xhr.abort)) {
  1258. request.cancelFunction = function () {
  1259. xhr.abort();
  1260. };
  1261. }
  1262. return deferred.promise;
  1263. };
  1264. const promise = RequestScheduler.request(request);
  1265. if (!defined(promise)) {
  1266. return;
  1267. }
  1268. return promise
  1269. .then(function (data) {
  1270. // explicitly set to undefined to ensure GC of request response data. See #8843
  1271. request.cancelFunction = undefined;
  1272. return data;
  1273. })
  1274. .catch(function (e) {
  1275. request.cancelFunction = undefined;
  1276. if (request.state !== RequestState.FAILED) {
  1277. return Promise.reject(e);
  1278. }
  1279. return resource.retryOnError(e).then(function (retry) {
  1280. if (retry) {
  1281. // Reset request so it can try again
  1282. request.state = RequestState.UNISSUED;
  1283. request.deferred = undefined;
  1284. return resource.fetch(options);
  1285. }
  1286. return Promise.reject(e);
  1287. });
  1288. });
  1289. };
  1290. /**
  1291. * Checks to make sure the Resource isn't already being requested.
  1292. *
  1293. * @param {Request} request The request to check.
  1294. *
  1295. * @private
  1296. */
  1297. function checkAndResetRequest(request) {
  1298. if (
  1299. request.state === RequestState.ISSUED ||
  1300. request.state === RequestState.ACTIVE
  1301. ) {
  1302. throw new RuntimeError("The Resource is already being fetched.");
  1303. }
  1304. request.state = RequestState.UNISSUED;
  1305. request.deferred = undefined;
  1306. }
  1307. const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
  1308. function decodeDataUriText(isBase64, data) {
  1309. const result = decodeURIComponent(data);
  1310. if (isBase64) {
  1311. return atob(result);
  1312. }
  1313. return result;
  1314. }
  1315. function decodeDataUriArrayBuffer(isBase64, data) {
  1316. const byteString = decodeDataUriText(isBase64, data);
  1317. const buffer = new ArrayBuffer(byteString.length);
  1318. const view = new Uint8Array(buffer);
  1319. for (let i = 0; i < byteString.length; i++) {
  1320. view[i] = byteString.charCodeAt(i);
  1321. }
  1322. return buffer;
  1323. }
  1324. function decodeDataUri(dataUriRegexResult, responseType) {
  1325. responseType = defaultValue(responseType, "");
  1326. const mimeType = dataUriRegexResult[1];
  1327. const isBase64 = !!dataUriRegexResult[2];
  1328. const data = dataUriRegexResult[3];
  1329. let buffer;
  1330. let parser;
  1331. switch (responseType) {
  1332. case "":
  1333. case "text":
  1334. return decodeDataUriText(isBase64, data);
  1335. case "arraybuffer":
  1336. return decodeDataUriArrayBuffer(isBase64, data);
  1337. case "blob":
  1338. buffer = decodeDataUriArrayBuffer(isBase64, data);
  1339. return new Blob([buffer], {
  1340. type: mimeType,
  1341. });
  1342. case "document":
  1343. parser = new DOMParser();
  1344. return parser.parseFromString(
  1345. decodeDataUriText(isBase64, data),
  1346. mimeType
  1347. );
  1348. case "json":
  1349. return JSON.parse(decodeDataUriText(isBase64, data));
  1350. default:
  1351. //>>includeStart('debug', pragmas.debug);
  1352. throw new DeveloperError(`Unhandled responseType: ${responseType}`);
  1353. //>>includeEnd('debug');
  1354. }
  1355. }
  1356. /**
  1357. * Asynchronously loads the given resource. Returns a promise that will resolve to
  1358. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1359. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1360. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. It's recommended that you use
  1361. * the more specific functions eg. fetchJson, fetchBlob, etc.
  1362. *
  1363. * @param {object} [options] Object with the following properties:
  1364. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1365. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1366. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1367. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1368. *
  1369. *
  1370. * @example
  1371. * resource.fetch()
  1372. * .then(function(body) {
  1373. * // use the data
  1374. * }).catch(function(error) {
  1375. * // an error occurred
  1376. * });
  1377. *
  1378. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1379. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1380. */
  1381. Resource.prototype.fetch = function (options) {
  1382. options = defaultClone(options, {});
  1383. options.method = "GET";
  1384. return this._makeRequest(options);
  1385. };
  1386. /**
  1387. * Creates a Resource from a URL and calls fetch() on it.
  1388. *
  1389. * @param {string|object} options A url or an object with the following properties
  1390. * @param {string} options.url The url of the resource.
  1391. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1392. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1393. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1394. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1395. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1396. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1397. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1398. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1399. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1400. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1401. */
  1402. Resource.fetch = function (options) {
  1403. const resource = new Resource(options);
  1404. return resource.fetch({
  1405. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1406. responseType: options.responseType,
  1407. overrideMimeType: options.overrideMimeType,
  1408. });
  1409. };
  1410. /**
  1411. * Asynchronously deletes the given resource. Returns a promise that will resolve to
  1412. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1413. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1414. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1415. *
  1416. * @param {object} [options] Object with the following properties:
  1417. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1418. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1419. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1420. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1421. *
  1422. *
  1423. * @example
  1424. * resource.delete()
  1425. * .then(function(body) {
  1426. * // use the data
  1427. * }).catch(function(error) {
  1428. * // an error occurred
  1429. * });
  1430. *
  1431. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1432. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1433. */
  1434. Resource.prototype.delete = function (options) {
  1435. options = defaultClone(options, {});
  1436. options.method = "DELETE";
  1437. return this._makeRequest(options);
  1438. };
  1439. /**
  1440. * Creates a Resource from a URL and calls delete() on it.
  1441. *
  1442. * @param {string|object} options A url or an object with the following properties
  1443. * @param {string} options.url The url of the resource.
  1444. * @param {object} [options.data] Data that is posted with the resource.
  1445. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1446. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1447. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1448. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1449. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1450. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1451. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1452. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1453. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1454. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1455. */
  1456. Resource.delete = function (options) {
  1457. const resource = new Resource(options);
  1458. return resource.delete({
  1459. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1460. responseType: options.responseType,
  1461. overrideMimeType: options.overrideMimeType,
  1462. data: options.data,
  1463. });
  1464. };
  1465. /**
  1466. * Asynchronously gets headers the given resource. Returns a promise that will resolve to
  1467. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1468. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1469. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1470. *
  1471. * @param {object} [options] Object with the following properties:
  1472. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1473. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1474. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1475. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1476. *
  1477. *
  1478. * @example
  1479. * resource.head()
  1480. * .then(function(headers) {
  1481. * // use the data
  1482. * }).catch(function(error) {
  1483. * // an error occurred
  1484. * });
  1485. *
  1486. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1487. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1488. */
  1489. Resource.prototype.head = function (options) {
  1490. options = defaultClone(options, {});
  1491. options.method = "HEAD";
  1492. return this._makeRequest(options);
  1493. };
  1494. /**
  1495. * Creates a Resource from a URL and calls head() on it.
  1496. *
  1497. * @param {string|object} options A url or an object with the following properties
  1498. * @param {string} options.url The url of the resource.
  1499. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1500. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1501. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1502. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1503. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1504. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1505. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1506. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1507. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1508. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1509. */
  1510. Resource.head = function (options) {
  1511. const resource = new Resource(options);
  1512. return resource.head({
  1513. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1514. responseType: options.responseType,
  1515. overrideMimeType: options.overrideMimeType,
  1516. });
  1517. };
  1518. /**
  1519. * Asynchronously gets options the given resource. Returns a promise that will resolve to
  1520. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1521. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1522. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1523. *
  1524. * @param {object} [options] Object with the following properties:
  1525. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1526. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1527. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1528. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1529. *
  1530. *
  1531. * @example
  1532. * resource.options()
  1533. * .then(function(headers) {
  1534. * // use the data
  1535. * }).catch(function(error) {
  1536. * // an error occurred
  1537. * });
  1538. *
  1539. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1540. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1541. */
  1542. Resource.prototype.options = function (options) {
  1543. options = defaultClone(options, {});
  1544. options.method = "OPTIONS";
  1545. return this._makeRequest(options);
  1546. };
  1547. /**
  1548. * Creates a Resource from a URL and calls options() on it.
  1549. *
  1550. * @param {string|object} options A url or an object with the following properties
  1551. * @param {string} options.url The url of the resource.
  1552. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1553. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1554. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1555. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1556. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1557. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1558. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1559. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1560. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1561. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1562. */
  1563. Resource.options = function (options) {
  1564. const resource = new Resource(options);
  1565. return resource.options({
  1566. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1567. responseType: options.responseType,
  1568. overrideMimeType: options.overrideMimeType,
  1569. });
  1570. };
  1571. /**
  1572. * Asynchronously posts data to the given resource. Returns a promise that will resolve to
  1573. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1574. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1575. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1576. *
  1577. * @param {object} data Data that is posted with the resource.
  1578. * @param {object} [options] Object with the following properties:
  1579. * @param {object} [options.data] Data that is posted with the resource.
  1580. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1581. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1582. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1583. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1584. *
  1585. *
  1586. * @example
  1587. * resource.post(data)
  1588. * .then(function(result) {
  1589. * // use the result
  1590. * }).catch(function(error) {
  1591. * // an error occurred
  1592. * });
  1593. *
  1594. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1595. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1596. */
  1597. Resource.prototype.post = function (data, options) {
  1598. Check.defined("data", data);
  1599. options = defaultClone(options, {});
  1600. options.method = "POST";
  1601. options.data = data;
  1602. return this._makeRequest(options);
  1603. };
  1604. /**
  1605. * Creates a Resource from a URL and calls post() on it.
  1606. *
  1607. * @param {object} options A url or an object with the following properties
  1608. * @param {string} options.url The url of the resource.
  1609. * @param {object} options.data Data that is posted with the resource.
  1610. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1611. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1612. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1613. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1614. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1615. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1616. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1617. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1618. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1619. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1620. */
  1621. Resource.post = function (options) {
  1622. const resource = new Resource(options);
  1623. return resource.post(options.data, {
  1624. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1625. responseType: options.responseType,
  1626. overrideMimeType: options.overrideMimeType,
  1627. });
  1628. };
  1629. /**
  1630. * Asynchronously puts data to the given resource. Returns a promise that will resolve to
  1631. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1632. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1633. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1634. *
  1635. * @param {object} data Data that is posted with the resource.
  1636. * @param {object} [options] Object with the following properties:
  1637. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1638. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1639. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1640. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1641. *
  1642. *
  1643. * @example
  1644. * resource.put(data)
  1645. * .then(function(result) {
  1646. * // use the result
  1647. * }).catch(function(error) {
  1648. * // an error occurred
  1649. * });
  1650. *
  1651. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1652. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1653. */
  1654. Resource.prototype.put = function (data, options) {
  1655. Check.defined("data", data);
  1656. options = defaultClone(options, {});
  1657. options.method = "PUT";
  1658. options.data = data;
  1659. return this._makeRequest(options);
  1660. };
  1661. /**
  1662. * Creates a Resource from a URL and calls put() on it.
  1663. *
  1664. * @param {object} options A url or an object with the following properties
  1665. * @param {string} options.url The url of the resource.
  1666. * @param {object} options.data Data that is posted with the resource.
  1667. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1668. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1669. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1670. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1671. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1672. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1673. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1674. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1675. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1676. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1677. */
  1678. Resource.put = function (options) {
  1679. const resource = new Resource(options);
  1680. return resource.put(options.data, {
  1681. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1682. responseType: options.responseType,
  1683. overrideMimeType: options.overrideMimeType,
  1684. });
  1685. };
  1686. /**
  1687. * Asynchronously patches data to the given resource. Returns a promise that will resolve to
  1688. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1689. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1690. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1691. *
  1692. * @param {object} data Data that is posted with the resource.
  1693. * @param {object} [options] Object with the following properties:
  1694. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1695. * @param {object} [options.headers] Additional HTTP headers to send with the request, if any.
  1696. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1697. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1698. *
  1699. *
  1700. * @example
  1701. * resource.patch(data)
  1702. * .then(function(result) {
  1703. * // use the result
  1704. * }).catch(function(error) {
  1705. * // an error occurred
  1706. * });
  1707. *
  1708. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1709. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1710. */
  1711. Resource.prototype.patch = function (data, options) {
  1712. Check.defined("data", data);
  1713. options = defaultClone(options, {});
  1714. options.method = "PATCH";
  1715. options.data = data;
  1716. return this._makeRequest(options);
  1717. };
  1718. /**
  1719. * Creates a Resource from a URL and calls patch() on it.
  1720. *
  1721. * @param {object} options A url or an object with the following properties
  1722. * @param {string} options.url The url of the resource.
  1723. * @param {object} options.data Data that is posted with the resource.
  1724. * @param {object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1725. * @param {object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1726. * @param {object} [options.headers={}] Additional HTTP headers that will be sent.
  1727. * @param {Proxy} [options.proxy] A proxy to be used when loading the resource.
  1728. * @param {Resource.RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1729. * @param {number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1730. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1731. * @param {string} [options.responseType] The type of response. This controls the type of item returned.
  1732. * @param {string} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1733. * @returns {Promise<any>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1734. */
  1735. Resource.patch = function (options) {
  1736. const resource = new Resource(options);
  1737. return resource.patch(options.data, {
  1738. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1739. responseType: options.responseType,
  1740. overrideMimeType: options.overrideMimeType,
  1741. });
  1742. };
  1743. /**
  1744. * Contains implementations of functions that can be replaced for testing
  1745. *
  1746. * @private
  1747. */
  1748. Resource._Implementations = {};
  1749. Resource._Implementations.loadImageElement = function (
  1750. url,
  1751. crossOrigin,
  1752. deferred
  1753. ) {
  1754. const image = new Image();
  1755. image.onload = function () {
  1756. // work-around a known issue with Firefox and dimensionless SVG, see:
  1757. // - https://github.com/whatwg/html/issues/3510
  1758. // - https://bugzilla.mozilla.org/show_bug.cgi?id=700533
  1759. if (
  1760. image.naturalWidth === 0 &&
  1761. image.naturalHeight === 0 &&
  1762. image.width === 0 &&
  1763. image.height === 0
  1764. ) {
  1765. // these values affect rasterization and will likely mar the content
  1766. // until Firefox takes a stance on the issue, marred content is better than no content
  1767. // Chromium uses a more refined heuristic about its choice given nil viewBox, and a better stance and solution is
  1768. // proposed later in the original issue thread:
  1769. // - Chromium behavior: https://github.com/CesiumGS/cesium/issues/9188#issuecomment-704400825
  1770. // - Cesium's stance/solve: https://github.com/CesiumGS/cesium/issues/9188#issuecomment-720645777
  1771. image.width = 300;
  1772. image.height = 150;
  1773. }
  1774. deferred.resolve(image);
  1775. };
  1776. image.onerror = function (e) {
  1777. deferred.reject(e);
  1778. };
  1779. if (crossOrigin) {
  1780. if (TrustedServers.contains(url)) {
  1781. image.crossOrigin = "use-credentials";
  1782. } else {
  1783. image.crossOrigin = "";
  1784. }
  1785. }
  1786. image.src = url;
  1787. };
  1788. Resource._Implementations.createImage = function (
  1789. request,
  1790. crossOrigin,
  1791. deferred,
  1792. flipY,
  1793. skipColorSpaceConversion,
  1794. preferImageBitmap
  1795. ) {
  1796. const url = request.url;
  1797. // Passing an Image to createImageBitmap will force it to run on the main thread
  1798. // since DOM elements don't exist on workers. We convert it to a blob so it's non-blocking.
  1799. // See:
  1800. // https://bugzilla.mozilla.org/show_bug.cgi?id=1044102#c38
  1801. // https://bugs.chromium.org/p/chromium/issues/detail?id=580202#c10
  1802. Resource.supportsImageBitmapOptions()
  1803. .then(function (supportsImageBitmap) {
  1804. // We can only use ImageBitmap if we can flip on decode.
  1805. // See: https://github.com/CesiumGS/cesium/pull/7579#issuecomment-466146898
  1806. if (!(supportsImageBitmap && preferImageBitmap)) {
  1807. Resource._Implementations.loadImageElement(url, crossOrigin, deferred);
  1808. return;
  1809. }
  1810. const responseType = "blob";
  1811. const method = "GET";
  1812. const xhrDeferred = defer();
  1813. const xhr = Resource._Implementations.loadWithXhr(
  1814. url,
  1815. responseType,
  1816. method,
  1817. undefined,
  1818. undefined,
  1819. xhrDeferred,
  1820. undefined,
  1821. undefined,
  1822. undefined
  1823. );
  1824. if (defined(xhr) && defined(xhr.abort)) {
  1825. request.cancelFunction = function () {
  1826. xhr.abort();
  1827. };
  1828. }
  1829. return xhrDeferred.promise
  1830. .then(function (blob) {
  1831. if (!defined(blob)) {
  1832. deferred.reject(
  1833. new RuntimeError(
  1834. `Successfully retrieved ${url} but it contained no content.`
  1835. )
  1836. );
  1837. return;
  1838. }
  1839. return Resource.createImageBitmapFromBlob(blob, {
  1840. flipY: flipY,
  1841. premultiplyAlpha: false,
  1842. skipColorSpaceConversion: skipColorSpaceConversion,
  1843. });
  1844. })
  1845. .then(function (image) {
  1846. deferred.resolve(image);
  1847. });
  1848. })
  1849. .catch(function (e) {
  1850. deferred.reject(e);
  1851. });
  1852. };
  1853. /**
  1854. * Wrapper for createImageBitmap
  1855. *
  1856. * @private
  1857. */
  1858. Resource.createImageBitmapFromBlob = function (blob, options) {
  1859. Check.defined("options", options);
  1860. Check.typeOf.bool("options.flipY", options.flipY);
  1861. Check.typeOf.bool("options.premultiplyAlpha", options.premultiplyAlpha);
  1862. Check.typeOf.bool(
  1863. "options.skipColorSpaceConversion",
  1864. options.skipColorSpaceConversion
  1865. );
  1866. return createImageBitmap(blob, {
  1867. imageOrientation: options.flipY ? "flipY" : "none",
  1868. premultiplyAlpha: options.premultiplyAlpha ? "premultiply" : "none",
  1869. colorSpaceConversion: options.skipColorSpaceConversion ? "none" : "default",
  1870. });
  1871. };
  1872. function decodeResponse(loadWithHttpResponse, responseType) {
  1873. switch (responseType) {
  1874. case "text":
  1875. return loadWithHttpResponse.toString("utf8");
  1876. case "json":
  1877. return JSON.parse(loadWithHttpResponse.toString("utf8"));
  1878. default:
  1879. return new Uint8Array(loadWithHttpResponse).buffer;
  1880. }
  1881. }
  1882. function loadWithHttpRequest(
  1883. url,
  1884. responseType,
  1885. method,
  1886. data,
  1887. headers,
  1888. deferred,
  1889. overrideMimeType
  1890. ) {
  1891. // Note: only the 'json' and 'text' responseTypes transforms the loaded buffer
  1892. let URL;
  1893. let zlib;
  1894. Promise.all([import("url"), import("zlib")])
  1895. .then(([urlImport, zlibImport]) => {
  1896. URL = urlImport.parse(url);
  1897. zlib = zlibImport;
  1898. return URL.protocol === "https:" ? import("https") : import("http");
  1899. })
  1900. .then((http) => {
  1901. const options = {
  1902. protocol: URL.protocol,
  1903. hostname: URL.hostname,
  1904. port: URL.port,
  1905. path: URL.path,
  1906. query: URL.query,
  1907. method: method,
  1908. headers: headers,
  1909. };
  1910. http
  1911. .request(options)
  1912. .on("response", function (res) {
  1913. if (res.statusCode < 200 || res.statusCode >= 300) {
  1914. deferred.reject(
  1915. new RequestErrorEvent(res.statusCode, res, res.headers)
  1916. );
  1917. return;
  1918. }
  1919. const chunkArray = [];
  1920. res.on("data", function (chunk) {
  1921. chunkArray.push(chunk);
  1922. });
  1923. res.on("end", function () {
  1924. // eslint-disable-next-line no-undef
  1925. const result = Buffer.concat(chunkArray);
  1926. if (res.headers["content-encoding"] === "gzip") {
  1927. zlib.gunzip(result, function (error, resultUnzipped) {
  1928. if (error) {
  1929. deferred.reject(
  1930. new RuntimeError("Error decompressing response.")
  1931. );
  1932. } else {
  1933. deferred.resolve(
  1934. decodeResponse(resultUnzipped, responseType)
  1935. );
  1936. }
  1937. });
  1938. } else {
  1939. deferred.resolve(decodeResponse(result, responseType));
  1940. }
  1941. });
  1942. })
  1943. .on("error", function (e) {
  1944. deferred.reject(new RequestErrorEvent());
  1945. })
  1946. .end();
  1947. });
  1948. }
  1949. const noXMLHttpRequest = typeof XMLHttpRequest === "undefined";
  1950. Resource._Implementations.loadWithXhr = function (
  1951. url,
  1952. responseType,
  1953. method,
  1954. data,
  1955. headers,
  1956. deferred,
  1957. overrideMimeType
  1958. ) {
  1959. const dataUriRegexResult = dataUriRegex.exec(url);
  1960. if (dataUriRegexResult !== null) {
  1961. deferred.resolve(decodeDataUri(dataUriRegexResult, responseType));
  1962. return;
  1963. }
  1964. if (noXMLHttpRequest) {
  1965. loadWithHttpRequest(
  1966. url,
  1967. responseType,
  1968. method,
  1969. data,
  1970. headers,
  1971. deferred,
  1972. overrideMimeType
  1973. );
  1974. return;
  1975. }
  1976. const xhr = new XMLHttpRequest();
  1977. if (TrustedServers.contains(url)) {
  1978. xhr.withCredentials = true;
  1979. }
  1980. xhr.open(method, url, true);
  1981. if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) {
  1982. xhr.overrideMimeType(overrideMimeType);
  1983. }
  1984. if (defined(headers)) {
  1985. for (const key in headers) {
  1986. if (headers.hasOwnProperty(key)) {
  1987. xhr.setRequestHeader(key, headers[key]);
  1988. }
  1989. }
  1990. }
  1991. if (defined(responseType)) {
  1992. xhr.responseType = responseType;
  1993. }
  1994. // While non-standard, file protocol always returns a status of 0 on success
  1995. let localFile = false;
  1996. if (typeof url === "string") {
  1997. localFile =
  1998. url.indexOf("file://") === 0 ||
  1999. (typeof window !== "undefined" && window.location.origin === "file://");
  2000. }
  2001. xhr.onload = function () {
  2002. if (
  2003. (xhr.status < 200 || xhr.status >= 300) &&
  2004. !(localFile && xhr.status === 0)
  2005. ) {
  2006. deferred.reject(
  2007. new RequestErrorEvent(
  2008. xhr.status,
  2009. xhr.response,
  2010. xhr.getAllResponseHeaders()
  2011. )
  2012. );
  2013. return;
  2014. }
  2015. const response = xhr.response;
  2016. const browserResponseType = xhr.responseType;
  2017. if (method === "HEAD" || method === "OPTIONS") {
  2018. const responseHeaderString = xhr.getAllResponseHeaders();
  2019. const splitHeaders = responseHeaderString.trim().split(/[\r\n]+/);
  2020. const responseHeaders = {};
  2021. splitHeaders.forEach(function (line) {
  2022. const parts = line.split(": ");
  2023. const header = parts.shift();
  2024. responseHeaders[header] = parts.join(": ");
  2025. });
  2026. deferred.resolve(responseHeaders);
  2027. return;
  2028. }
  2029. //All modern browsers will go into either the first or second if block or last else block.
  2030. //Other code paths support older browsers that either do not support the supplied responseType
  2031. //or do not support the xhr.response property.
  2032. if (xhr.status === 204) {
  2033. // accept no content
  2034. deferred.resolve();
  2035. } else if (
  2036. defined(response) &&
  2037. (!defined(responseType) || browserResponseType === responseType)
  2038. ) {
  2039. deferred.resolve(response);
  2040. } else if (responseType === "json" && typeof response === "string") {
  2041. try {
  2042. deferred.resolve(JSON.parse(response));
  2043. } catch (e) {
  2044. deferred.reject(e);
  2045. }
  2046. } else if (
  2047. (browserResponseType === "" || browserResponseType === "document") &&
  2048. defined(xhr.responseXML) &&
  2049. xhr.responseXML.hasChildNodes()
  2050. ) {
  2051. deferred.resolve(xhr.responseXML);
  2052. } else if (
  2053. (browserResponseType === "" || browserResponseType === "text") &&
  2054. defined(xhr.responseText)
  2055. ) {
  2056. deferred.resolve(xhr.responseText);
  2057. } else {
  2058. deferred.reject(
  2059. new RuntimeError("Invalid XMLHttpRequest response type.")
  2060. );
  2061. }
  2062. };
  2063. xhr.onerror = function (e) {
  2064. deferred.reject(new RequestErrorEvent());
  2065. };
  2066. xhr.send(data);
  2067. return xhr;
  2068. };
  2069. Resource._Implementations.loadAndExecuteScript = function (
  2070. url,
  2071. functionName,
  2072. deferred
  2073. ) {
  2074. return loadAndExecuteScript(url, functionName).catch(function (e) {
  2075. deferred.reject(e);
  2076. });
  2077. };
  2078. /**
  2079. * The default implementations
  2080. *
  2081. * @private
  2082. */
  2083. Resource._DefaultImplementations = {};
  2084. Resource._DefaultImplementations.createImage =
  2085. Resource._Implementations.createImage;
  2086. Resource._DefaultImplementations.loadWithXhr =
  2087. Resource._Implementations.loadWithXhr;
  2088. Resource._DefaultImplementations.loadAndExecuteScript =
  2089. Resource._Implementations.loadAndExecuteScript;
  2090. /**
  2091. * A resource instance initialized to the current browser location
  2092. *
  2093. * @type {Resource}
  2094. * @constant
  2095. */
  2096. Resource.DEFAULT = Object.freeze(
  2097. new Resource({
  2098. url:
  2099. typeof document === "undefined"
  2100. ? ""
  2101. : document.location.href.split("?")[0],
  2102. })
  2103. );
  2104. /**
  2105. * A function that returns the value of the property.
  2106. * @callback Resource.RetryCallback
  2107. *
  2108. * @param {Resource} [resource] The resource that failed to load.
  2109. * @param {RequestErrorEvent} [error] The error that occurred during the loading of the resource.
  2110. * @returns {boolean|Promise<boolean>} If true or a promise that resolved to true, the resource will be retried. Otherwise the failure will be returned.
  2111. */
  2112. export default Resource;