@loaders.gl/services 5.0.0-alpha.5 → 5.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loaders.gl/services",
3
- "version": "5.0.0-alpha.5",
3
+ "version": "5.0.0-alpha.7",
4
4
  "description": "Framework-independent loaders and sources for geospatial services",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,6 +36,10 @@
36
36
  "./bundled": {
37
37
  "types": "./dist/bundled.d.ts",
38
38
  "import": "./dist/bundled.js"
39
+ },
40
+ "./authentication": {
41
+ "types": "./dist/authentication.d.ts",
42
+ "import": "./dist/authentication.js"
39
43
  }
40
44
  },
41
45
  "sideEffects": false,
@@ -45,16 +49,16 @@
45
49
  "README.md"
46
50
  ],
47
51
  "dependencies": {
48
- "@loaders.gl/gis": "5.0.0-alpha.5",
49
- "@loaders.gl/i3s": "5.0.0-alpha.5",
50
- "@loaders.gl/images": "5.0.0-alpha.5",
51
- "@loaders.gl/lerc": "5.0.0-alpha.5",
52
- "@loaders.gl/loader-utils": "5.0.0-alpha.5",
53
- "@loaders.gl/mvt": "5.0.0-alpha.5",
54
- "@loaders.gl/schema": "5.0.0-alpha.5"
52
+ "@loaders.gl/gis": "5.0.0-alpha.7",
53
+ "@loaders.gl/i3s": "5.0.0-alpha.7",
54
+ "@loaders.gl/images": "5.0.0-alpha.7",
55
+ "@loaders.gl/lerc": "5.0.0-alpha.7",
56
+ "@loaders.gl/loader-utils": "5.0.0-alpha.7",
57
+ "@loaders.gl/mvt": "5.0.0-alpha.7",
58
+ "@loaders.gl/schema": "5.0.0-alpha.7"
55
59
  },
56
60
  "peerDependencies": {
57
61
  "@loaders.gl/core": "~5.0.0-alpha.0"
58
62
  },
59
- "gitHead": "0f6abae409f8d987337f5d7b7e6ba47bf72123b0"
63
+ "gitHead": "e1e6174f9e66f4f3e695e8778f769429508342f1"
60
64
  }
@@ -155,7 +155,9 @@ export class ArcGISVectorSource
155
155
  return geoJsonTable;
156
156
  case 'arrow':
157
157
  default:
158
- return convertFeaturesToWKBArrowTable(geoJsonTable.features);
158
+ return convertFeaturesToWKBArrowTable(geoJsonTable.features, {
159
+ encodingPreference: parameters.geoarrow?.encodingPreference
160
+ });
159
161
  }
160
162
  }
161
163
 
@@ -182,13 +184,15 @@ export class ArcGISVectorSource
182
184
  /** Builds a query URL from generic vector source parameters. */
183
185
  getFeaturesURL(parameters: GetFeaturesParameters): string {
184
186
  const defaultParameters = this.options['arcgis-feature-server']?.queryParameters || {};
185
- const spatialReference = normalizeArcGISSpatialReference(parameters.crs) || 4326;
187
+ const outputSpatialReference = normalizeArcGISSpatialReference(parameters.crs) || 4326;
188
+ const requestSpatialReference =
189
+ normalizeArcGISSpatialReference(parameters.requestCrs) || outputSpatialReference;
186
190
  const queryParameters: ArcGISFeatureServiceQueryOptions = {
187
191
  returnGeometry: true,
188
192
  where: '1=1',
189
193
  outFields: '*',
190
- outSR: spatialReference,
191
- inSR: spatialReference,
194
+ outSR: outputSpatialReference,
195
+ inSR: requestSpatialReference,
192
196
  f: 'geojson',
193
197
  ...defaultParameters
194
198
  };
@@ -114,10 +114,10 @@ export class ArcGISImageSource
114
114
 
115
115
  /** Requests an image from generic ImageSource parameters. */
116
116
  async getImage(parameters: GetImageParameters): Promise<ImageType> {
117
- const {boundingBox, bbox, width, height, crs, format, signal} = parameters;
117
+ const {boundingBox, width, height, crs, format, signal} = parameters;
118
118
  const spatialReference = normalizeArcGISSpatialReference(crs) || '4326';
119
119
  const imageParameters: ArcGISExportImageParameters = {
120
- bbox: boundingBox ? [...boundingBox[0], ...boundingBox[1]] : bbox!,
120
+ bbox: [...boundingBox[0], ...boundingBox[1]],
121
121
  bboxSR: spatialReference,
122
122
  imageSR: spatialReference,
123
123
  width,
@@ -2,12 +2,24 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {RequestCredential, TokenValue} from '@loaders.gl/loader-utils';
5
+ import type {
6
+ FetchLike,
7
+ RequestCredential,
8
+ TokenProviderContext,
9
+ TokenValue
10
+ } from '@loaders.gl/loader-utils';
6
11
  import {
12
+ createAuthenticatedFetch,
7
13
  createBearerTokenCredential,
8
14
  createQueryParameterCredential
9
15
  } from '@loaders.gl/loader-utils';
10
16
 
17
+ const CESIUM_ION_API_ORIGIN = 'https://api.cesium.com';
18
+ const CESIUM_ION_ASSET_ORIGINS = [
19
+ 'https://assets.ion.cesium.com',
20
+ 'https://assets.cesium.com'
21
+ ] as const;
22
+
11
23
  /** Shared options for provider credential presets with known public API origins. */
12
24
  export type ServiceCredentialOptions = {
13
25
  /** Replaces the provider's default exact-origin allowlist. */
@@ -38,6 +50,12 @@ export type GoogleMapsCredentialOptions = ServiceCredentialOptions & {
38
50
  export type CesiumIonCredentialOptions = ServiceCredentialOptions & {
39
51
  /** Cesium ion access token or application-managed token callback. */
40
52
  accessToken: TokenValue;
53
+ /** Optional asset whose endpoint-scoped token should be resolved. */
54
+ assetId?: number | string;
55
+ /** Overrides the transport used for the ion endpoint-token exchange. */
56
+ fetch?: FetchLike;
57
+ /** Whether the supplied token is account-scoped or already asset-scoped. */
58
+ tokenType?: 'account' | 'asset' | 'auto';
41
59
  };
42
60
 
43
61
  /** Creates an exact-origin ArcGIS `token` query credential. */
@@ -73,11 +91,98 @@ export function createGoogleMapsCredential(
73
91
  });
74
92
  }
75
93
 
76
- /** Creates a bearer credential for the Cesium ion REST API. */
94
+ /**
95
+ * Creates a bearer credential for the Cesium ion REST API or one Cesium ion asset.
96
+ *
97
+ * Supplying `assetId` creates an asset-origin credential. Account tokens are exchanged lazily
98
+ * through Cesium ion's endpoint API; legacy asset-scoped tokens are used directly. Without an
99
+ * `assetId`, the credential is scoped only to the ion REST API for backwards compatibility.
100
+ */
77
101
  export function createCesiumIonCredential(options: CesiumIonCredentialOptions): RequestCredential {
102
+ if (options.assetId === undefined || options.assetId === null) {
103
+ return createBearerTokenCredential({
104
+ id: 'cesium-ion-access-token',
105
+ origins: options.origins || [CESIUM_ION_API_ORIGIN],
106
+ token: options.accessToken
107
+ });
108
+ }
109
+
110
+ const assetId = options.assetId;
111
+ const origins = options.origins || CESIUM_ION_ASSET_ORIGINS;
112
+ let endpointTokenPromise: Promise<string | null> | null = null;
113
+
78
114
  return createBearerTokenCredential({
79
- id: 'cesium-ion-access-token',
80
- origins: options.origins || ['https://api.cesium.com'],
81
- token: options.accessToken
115
+ id: `cesium-ion-asset-${assetId}`,
116
+ origins,
117
+ token: context => {
118
+ if (context.reason === 'refresh') {
119
+ endpointTokenPromise = null;
120
+ }
121
+ endpointTokenPromise ||= resolveCesiumIonAssetToken(options, assetId, origins, context);
122
+ return endpointTokenPromise;
123
+ }
124
+ });
125
+ }
126
+
127
+ /** Resolves an account token and exchanges it for an asset endpoint token when required. */
128
+ async function resolveCesiumIonAssetToken(
129
+ options: CesiumIonCredentialOptions,
130
+ assetId: number | string,
131
+ origins: readonly string[],
132
+ context: TokenProviderContext
133
+ ): Promise<string | null> {
134
+ const endpointURL = `${CESIUM_ION_API_ORIGIN}/v1/assets/${assetId}/endpoint`;
135
+ const accessToken =
136
+ typeof options.accessToken === 'function'
137
+ ? await options.accessToken({...context, url: endpointURL})
138
+ : options.accessToken;
139
+ if (!accessToken) return null;
140
+
141
+ const tokenType = options.tokenType || 'auto';
142
+ if (
143
+ tokenType === 'asset' ||
144
+ (tokenType === 'auto' && isCesiumIonAssetToken(accessToken, assetId))
145
+ ) {
146
+ return accessToken;
147
+ }
148
+
149
+ const apiCredential = createBearerTokenCredential({
150
+ id: `cesium-ion-api-${assetId}`,
151
+ origins: [CESIUM_ION_API_ORIGIN],
152
+ token: accessToken
153
+ });
154
+ const ionFetch = createAuthenticatedFetch({
155
+ fetch: options.fetch,
156
+ credentials: [apiCredential]
82
157
  });
158
+ const response = await ionFetch(endpointURL);
159
+ if (!response.ok) {
160
+ throw new Error(`Cesium ion asset ${assetId} endpoint request failed: ${response.status}`);
161
+ }
162
+
163
+ const endpoint = (await response.json()) as {accessToken?: unknown; url?: unknown};
164
+ if (typeof endpoint.url !== 'string' || !origins.includes(new URL(endpoint.url).origin)) {
165
+ throw new Error(`Cesium ion asset ${assetId} resolved to an unconfigured origin.`);
166
+ }
167
+ if (typeof endpoint.accessToken !== 'string' || !endpoint.accessToken) {
168
+ throw new Error(`Cesium ion did not return an endpoint token for asset ${assetId}.`);
169
+ }
170
+ return endpoint.accessToken;
171
+ }
172
+
173
+ /** Detects legacy JWTs whose claims scope them directly to the requested asset. */
174
+ function isCesiumIonAssetToken(token: string, assetId: number | string): boolean {
175
+ try {
176
+ const encodedClaims = token.split('.')[1];
177
+ if (!encodedClaims) return false;
178
+ const base64 = encodedClaims.replace(/-/g, '+').replace(/_/g, '/');
179
+ const paddedBase64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
180
+ const claims = JSON.parse(atob(paddedBase64)) as {assets?: unknown};
181
+ return (
182
+ Array.isArray(claims.assets) &&
183
+ claims.assets.some(claimAssetId => String(claimAssetId) === String(assetId))
184
+ );
185
+ } catch {
186
+ return false;
187
+ }
83
188
  }