IonResource.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import Uri from "urijs";
  2. import Check from "./Check.js";
  3. import Credit from "./Credit.js";
  4. import defaultValue from "./defaultValue.js";
  5. import defined from "./defined.js";
  6. import Ion from "./Ion.js";
  7. import Resource from "./Resource.js";
  8. import RuntimeError from "./RuntimeError.js";
  9. /**
  10. * A {@link Resource} instance that encapsulates Cesium ion asset access.
  11. * This object is normally not instantiated directly, use {@link IonResource.fromAssetId}.
  12. *
  13. * @alias IonResource
  14. * @constructor
  15. * @augments Resource
  16. *
  17. * @param {object} endpoint The result of the Cesium ion asset endpoint service.
  18. * @param {Resource} endpointResource The resource used to retrieve the endpoint.
  19. *
  20. * @see Ion
  21. * @see IonImageryProvider
  22. * @see createWorldTerrain
  23. * @see https://cesium.com
  24. */
  25. function IonResource(endpoint, endpointResource) {
  26. //>>includeStart('debug', pragmas.debug);
  27. Check.defined("endpoint", endpoint);
  28. Check.defined("endpointResource", endpointResource);
  29. //>>includeEnd('debug');
  30. let options;
  31. const externalType = endpoint.externalType;
  32. const isExternal = defined(externalType);
  33. if (!isExternal) {
  34. options = {
  35. url: endpoint.url,
  36. retryAttempts: 1,
  37. retryCallback: retryCallback,
  38. };
  39. } else if (
  40. externalType === "3DTILES" ||
  41. externalType === "STK_TERRAIN_SERVER"
  42. ) {
  43. // 3D Tiles and STK Terrain Server external assets can still be represented as an IonResource
  44. options = { url: endpoint.options.url };
  45. } else {
  46. //External imagery assets have additional configuration that can't be represented as a Resource
  47. throw new RuntimeError(
  48. "Ion.createResource does not support external imagery assets; use IonImageryProvider instead."
  49. );
  50. }
  51. Resource.call(this, options);
  52. // The asset endpoint data returned from ion.
  53. this._ionEndpoint = endpoint;
  54. this._ionEndpointDomain = isExternal
  55. ? undefined
  56. : new Uri(endpoint.url).authority();
  57. // The endpoint resource to fetch when a new token is needed
  58. this._ionEndpointResource = endpointResource;
  59. // The primary IonResource from which an instance is derived
  60. this._ionRoot = undefined;
  61. // Shared promise for endpooint requests amd credits (only ever set on the root request)
  62. this._pendingPromise = undefined;
  63. this._credits = undefined;
  64. this._isExternal = isExternal;
  65. }
  66. if (defined(Object.create)) {
  67. IonResource.prototype = Object.create(Resource.prototype);
  68. IonResource.prototype.constructor = IonResource;
  69. }
  70. /**
  71. * Asynchronously creates an instance.
  72. *
  73. * @param {number} assetId The Cesium ion asset id.
  74. * @param {object} [options] An object with the following properties:
  75. * @param {string} [options.accessToken=Ion.defaultAccessToken] The access token to use.
  76. * @param {string|Resource} [options.server=Ion.defaultServer] The resource to the Cesium ion API server.
  77. * @returns {Promise<IonResource>} A Promise to am instance representing the Cesium ion Asset.
  78. *
  79. * @example
  80. * // Load a Cesium3DTileset with asset ID of 124624234
  81. * try {
  82. * const resource = await Cesium.IonResource.fromAssetId(124624234);
  83. * const tileset = await Cesium.Cesium3DTileset.fromUrl(resource);
  84. * scene.primitives.add(tileset);
  85. * } catch (error) {
  86. * console.error(`Error creating tileset: ${error}`);
  87. * }
  88. *
  89. * @example
  90. * //Load a CZML file with asset ID of 10890
  91. * Cesium.IonResource.fromAssetId(10890)
  92. * .then(function (resource) {
  93. * viewer.dataSources.add(Cesium.CzmlDataSource.load(resource));
  94. * });
  95. */
  96. IonResource.fromAssetId = function (assetId, options) {
  97. const endpointResource = IonResource._createEndpointResource(
  98. assetId,
  99. options
  100. );
  101. return endpointResource.fetchJson().then(function (endpoint) {
  102. return new IonResource(endpoint, endpointResource);
  103. });
  104. };
  105. Object.defineProperties(IonResource.prototype, {
  106. /**
  107. * Gets the credits required for attribution of the asset.
  108. *
  109. * @memberof IonResource.prototype
  110. * @type {Credit[]}
  111. * @readonly
  112. */
  113. credits: {
  114. get: function () {
  115. // Only we're not the root, return its credits;
  116. if (defined(this._ionRoot)) {
  117. return this._ionRoot.credits;
  118. }
  119. // We are the root
  120. if (defined(this._credits)) {
  121. return this._credits;
  122. }
  123. this._credits = IonResource.getCreditsFromEndpoint(
  124. this._ionEndpoint,
  125. this._ionEndpointResource
  126. );
  127. return this._credits;
  128. },
  129. },
  130. });
  131. /** @private */
  132. IonResource.getCreditsFromEndpoint = function (endpoint, endpointResource) {
  133. const credits = endpoint.attributions.map(Credit.getIonCredit);
  134. const defaultTokenCredit = Ion.getDefaultTokenCredit(
  135. endpointResource.queryParameters.access_token
  136. );
  137. if (defined(defaultTokenCredit)) {
  138. credits.push(Credit.clone(defaultTokenCredit));
  139. }
  140. return credits;
  141. };
  142. /** @inheritdoc */
  143. IonResource.prototype.clone = function (result) {
  144. // We always want to use the root's information because it's the most up-to-date
  145. const ionRoot = defaultValue(this._ionRoot, this);
  146. if (!defined(result)) {
  147. result = new IonResource(
  148. ionRoot._ionEndpoint,
  149. ionRoot._ionEndpointResource
  150. );
  151. }
  152. result = Resource.prototype.clone.call(this, result);
  153. result._ionRoot = ionRoot;
  154. result._isExternal = this._isExternal;
  155. return result;
  156. };
  157. IonResource.prototype.fetchImage = function (options) {
  158. if (!this._isExternal) {
  159. const userOptions = options;
  160. options = {
  161. preferBlob: true,
  162. };
  163. if (defined(userOptions)) {
  164. options.flipY = userOptions.flipY;
  165. options.preferImageBitmap = userOptions.preferImageBitmap;
  166. }
  167. }
  168. return Resource.prototype.fetchImage.call(this, options);
  169. };
  170. IonResource.prototype._makeRequest = function (options) {
  171. // Don't send ion access token to non-ion servers.
  172. if (
  173. this._isExternal ||
  174. new Uri(this.url).authority() !== this._ionEndpointDomain
  175. ) {
  176. return Resource.prototype._makeRequest.call(this, options);
  177. }
  178. if (!defined(options.headers)) {
  179. options.headers = {};
  180. }
  181. options.headers.Authorization = `Bearer ${this._ionEndpoint.accessToken}`;
  182. options.headers["X-Cesium-Client"] = "CesiumJS";
  183. /* global CESIUM_VERSION */
  184. if (typeof CESIUM_VERSION !== "undefined") {
  185. options.headers["X-Cesium-Client-Version"] = CESIUM_VERSION;
  186. }
  187. return Resource.prototype._makeRequest.call(this, options);
  188. };
  189. /**
  190. * @private
  191. */
  192. IonResource._createEndpointResource = function (assetId, options) {
  193. //>>includeStart('debug', pragmas.debug);
  194. Check.defined("assetId", assetId);
  195. //>>includeEnd('debug');
  196. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  197. let server = defaultValue(options.server, Ion.defaultServer);
  198. const accessToken = defaultValue(options.accessToken, Ion.defaultAccessToken);
  199. server = Resource.createIfNeeded(server);
  200. const resourceOptions = {
  201. url: `v1/assets/${assetId}/endpoint`,
  202. };
  203. if (defined(accessToken)) {
  204. resourceOptions.queryParameters = { access_token: accessToken };
  205. }
  206. return server.getDerivedResource(resourceOptions);
  207. };
  208. function retryCallback(that, error) {
  209. const ionRoot = defaultValue(that._ionRoot, that);
  210. const endpointResource = ionRoot._ionEndpointResource;
  211. // Image is not available in worker threads, so this avoids
  212. // a ReferenceError
  213. const imageDefined = typeof Image !== "undefined";
  214. // We only want to retry in the case of invalid credentials (401) or image
  215. // requests(since Image failures can not provide a status code)
  216. if (
  217. !defined(error) ||
  218. (error.statusCode !== 401 &&
  219. !(imageDefined && error.target instanceof Image))
  220. ) {
  221. return Promise.resolve(false);
  222. }
  223. // We use a shared pending promise for all derived assets, since they share
  224. // a common access_token. If we're already requesting a new token for this
  225. // asset, we wait on the same promise.
  226. if (!defined(ionRoot._pendingPromise)) {
  227. ionRoot._pendingPromise = endpointResource
  228. .fetchJson()
  229. .then(function (newEndpoint) {
  230. //Set the token for root resource so new derived resources automatically pick it up
  231. ionRoot._ionEndpoint = newEndpoint;
  232. return newEndpoint;
  233. })
  234. .finally(function (newEndpoint) {
  235. // Pass or fail, we're done with this promise, the next failure should use a new one.
  236. ionRoot._pendingPromise = undefined;
  237. return newEndpoint;
  238. });
  239. }
  240. return ionRoot._pendingPromise.then(function (newEndpoint) {
  241. // Set the new token and endpoint for this resource
  242. that._ionEndpoint = newEndpoint;
  243. return true;
  244. });
  245. }
  246. export default IonResource;