@carto/api-client 0.5.30-alpha.b195e7f.118 → 0.5.30
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/CHANGELOG.md +2 -0
- package/build/api-client.cjs +113 -20
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +82 -19
- package/build/api-client.d.ts +82 -19
- package/build/api-client.js +108 -20
- package/build/api-client.js.map +1 -1
- package/build/worker-compat.js +2 -1
- package/build/worker-compat.js.map +1 -1
- package/build/worker.js +2 -1
- package/build/worker.js.map +1 -1
- package/package.json +1 -1
- package/src/api/auth.ts +92 -0
- package/src/api/index.ts +7 -0
- package/src/api/query.ts +3 -1
- package/src/api/request-with-parameters.ts +32 -5
- package/src/fetch-map/parse-map.ts +7 -1
- package/src/index.ts +6 -0
- package/src/models/common.ts +12 -2
- package/src/models/model.ts +8 -4
- package/src/sources/base-source.ts +26 -3
- package/src/sources/types.ts +9 -5
- package/src/spatial-index.ts +41 -0
- package/src/types.ts +12 -13
- package/src/widget-sources/widget-remote-source.ts +4 -1
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"homepage": "https://github.com/CartoDB/carto-api-client#readme",
|
|
9
9
|
"author": "Don McCurdy <donmccurdy@carto.com>",
|
|
10
10
|
"packageManager": "yarn@4.3.1",
|
|
11
|
-
"version": "0.5.30
|
|
11
|
+
"version": "0.5.30",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
package/src/api/auth.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How requests to the CARTO APIs are authenticated.
|
|
3
|
+
*
|
|
4
|
+
* - `'token'` (default): every request carries `Authorization: Bearer <accessToken>`.
|
|
5
|
+
* - `'session'`: requests carry NO Authorization header and are sent with
|
|
6
|
+
* `credentials: 'same-origin'`. Authentication is delegated to the server
|
|
7
|
+
* behind `apiBaseUrl` — typically a same-origin proxy that attaches the
|
|
8
|
+
* credential server-side from a session cookie. Used by CARTO hosted apps
|
|
9
|
+
* (`apiBaseUrl: '/app/_proxy/<slug>'`) and other single-origin deployments
|
|
10
|
+
* where the access token must never be exposed to JavaScript.
|
|
11
|
+
*/
|
|
12
|
+
export type AuthMode = 'token' | 'session';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Authentication options shared by sources, queries, widget sources and
|
|
16
|
+
* fetchMap:
|
|
17
|
+
*
|
|
18
|
+
* - token mode (default): `accessToken` is required.
|
|
19
|
+
* - session mode: `authMode: 'session'` and no `accessToken`.
|
|
20
|
+
*
|
|
21
|
+
* Validated at request time by {@link buildAuthHeaders}.
|
|
22
|
+
*/
|
|
23
|
+
export type AuthOptions = {
|
|
24
|
+
/** Carto platform access token. Required unless `authMode` is `'session'`. */
|
|
25
|
+
accessToken?: string;
|
|
26
|
+
/** @default 'token' */
|
|
27
|
+
authMode?: AuthMode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Builds the Authorization headers for a request: a Bearer header in token
|
|
32
|
+
* mode, no auth headers at all in session mode (the server-side session
|
|
33
|
+
* middleware must see the header absent to engage). Also validates the
|
|
34
|
+
* options, so every request path fails fast on a misconfiguration.
|
|
35
|
+
*/
|
|
36
|
+
export function buildAuthHeaders(options: AuthOptions): Record<string, string> {
|
|
37
|
+
if (options.authMode === 'session') {
|
|
38
|
+
if (options.accessToken) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`accessToken must not be provided with authMode: 'session' — ` +
|
|
41
|
+
`authentication is delegated to the server behind apiBaseUrl`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
if (!options.accessToken) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`accessToken is required (or pass authMode: 'session' to authenticate ` +
|
|
49
|
+
`via a same-origin session instead)`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return {Authorization: `Bearer ${options.accessToken}`};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `fetch()` credentials for the given auth mode. Session mode must send the
|
|
57
|
+
* cookie explicitly; token mode preserves today's behavior (unset).
|
|
58
|
+
*/
|
|
59
|
+
export function getAuthCredentials(
|
|
60
|
+
authMode?: AuthMode
|
|
61
|
+
): RequestCredentials | undefined {
|
|
62
|
+
return authMode === 'session' ? 'same-origin' : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Rewrites an absolute CARTO API URL (e.g. a tilejson `tiles[]` template or
|
|
67
|
+
* the map-instantiation `dataUrl`, which always point at the tenant API host)
|
|
68
|
+
* onto the configured `apiBaseUrl`. In session mode the browser cannot reach
|
|
69
|
+
* the API host directly — every request must go through the same-origin
|
|
70
|
+
* proxy that `apiBaseUrl` points at. Relative URLs are returned unchanged.
|
|
71
|
+
*/
|
|
72
|
+
export function rewriteUrlForSessionMode(
|
|
73
|
+
url: string,
|
|
74
|
+
apiBaseUrl: string
|
|
75
|
+
): string {
|
|
76
|
+
let origin: string;
|
|
77
|
+
try {
|
|
78
|
+
const parsed = new URL(url);
|
|
79
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
80
|
+
return url;
|
|
81
|
+
}
|
|
82
|
+
origin = parsed.origin;
|
|
83
|
+
} catch {
|
|
84
|
+
// Relative URL — already same-origin, nothing to rewrite.
|
|
85
|
+
return url;
|
|
86
|
+
}
|
|
87
|
+
// Keep the path+query as a raw slice rather than re-serializing through
|
|
88
|
+
// URL: tile URL templates contain literal placeholders ({z}/{x}/{y}) that
|
|
89
|
+
// URL#pathname would percent-encode, breaking template substitution.
|
|
90
|
+
const base = apiBaseUrl.replace(/\/+$/, '');
|
|
91
|
+
return base + url.slice(origin.length);
|
|
92
|
+
}
|
package/src/api/index.ts
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
+
export {
|
|
6
|
+
buildAuthHeaders,
|
|
7
|
+
getAuthCredentials,
|
|
8
|
+
rewriteUrlForSessionMode,
|
|
9
|
+
type AuthMode,
|
|
10
|
+
type AuthOptions,
|
|
11
|
+
} from './auth.js';
|
|
5
12
|
export {
|
|
6
13
|
CartoAPIError,
|
|
7
14
|
type APIErrorContext,
|
package/src/api/query.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
QueryResult,
|
|
10
10
|
} from '../sources/types.js';
|
|
11
11
|
import {buildQueryUrl} from './endpoints.js';
|
|
12
|
+
import {buildAuthHeaders, getAuthCredentials} from './auth.js';
|
|
12
13
|
import {requestWithParameters} from './request-with-parameters.js';
|
|
13
14
|
import type {APIErrorContext} from './carto-api-error.js';
|
|
14
15
|
import {getClient} from '../client.js';
|
|
@@ -50,7 +51,7 @@ export const query = async function (
|
|
|
50
51
|
|
|
51
52
|
const baseUrl = buildQueryUrl({apiBaseUrl, connectionName});
|
|
52
53
|
const headers = {
|
|
53
|
-
|
|
54
|
+
...buildAuthHeaders(options),
|
|
54
55
|
...options.headers,
|
|
55
56
|
};
|
|
56
57
|
const parameters = {
|
|
@@ -74,5 +75,6 @@ export const query = async function (
|
|
|
74
75
|
maxLengthURL,
|
|
75
76
|
localCache,
|
|
76
77
|
signal: options.signal,
|
|
78
|
+
credentials: getAuthCredentials(options.authMode),
|
|
77
79
|
});
|
|
78
80
|
};
|
|
@@ -24,6 +24,7 @@ export async function requestWithParameters<T = any>({
|
|
|
24
24
|
maxLengthURL = DEFAULT_MAX_LENGTH_URL,
|
|
25
25
|
localCache,
|
|
26
26
|
signal,
|
|
27
|
+
credentials,
|
|
27
28
|
}: {
|
|
28
29
|
baseUrl: string;
|
|
29
30
|
parameters?: Record<string, unknown>;
|
|
@@ -32,6 +33,8 @@ export async function requestWithParameters<T = any>({
|
|
|
32
33
|
maxLengthURL?: number;
|
|
33
34
|
localCache?: LocalCacheOptions;
|
|
34
35
|
signal?: AbortSignal;
|
|
36
|
+
/** Forwarded to `fetch()`. Session auth mode passes 'same-origin'. */
|
|
37
|
+
credentials?: RequestCredentials;
|
|
35
38
|
}): Promise<T> {
|
|
36
39
|
// Parameters added to all requests issued with `requestWithParameters()`.
|
|
37
40
|
// These parameters override parameters already in the base URL, but not
|
|
@@ -68,8 +71,9 @@ export async function requestWithParameters<T = any>({
|
|
|
68
71
|
body: JSON.stringify(parameters),
|
|
69
72
|
headers,
|
|
70
73
|
signal,
|
|
74
|
+
...(credentials && {credentials}),
|
|
71
75
|
})
|
|
72
|
-
: fetch(url, {headers, signal});
|
|
76
|
+
: fetch(url, {headers, signal, ...(credentials && {credentials})});
|
|
73
77
|
|
|
74
78
|
let response: Response | undefined;
|
|
75
79
|
let responseJson: unknown;
|
|
@@ -132,6 +136,29 @@ function createCacheKey(
|
|
|
132
136
|
});
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Base URLs may be relative (e.g. a same-origin proxy like
|
|
141
|
+
* '/app/_proxy/<slug>/v3/...' used with session auth mode). `new URL()`
|
|
142
|
+
* requires an origin, so relative URLs are resolved against a marker origin
|
|
143
|
+
* that is stripped back off before returning.
|
|
144
|
+
*/
|
|
145
|
+
const RELATIVE_ORIGIN = 'https://relative.invalid';
|
|
146
|
+
|
|
147
|
+
function parseBaseUrl(baseUrlString: string): {url: URL; isRelative: boolean} {
|
|
148
|
+
// Protocol-relative URLs ('//host/...') carry a host and must not be treated
|
|
149
|
+
// as relative, or the host would be lost when resolved against RELATIVE_ORIGIN.
|
|
150
|
+
const isRelative =
|
|
151
|
+
baseUrlString.startsWith('/') && !baseUrlString.startsWith('//');
|
|
152
|
+
return {
|
|
153
|
+
url: new URL(baseUrlString, isRelative ? RELATIVE_ORIGIN : undefined),
|
|
154
|
+
isRelative,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function serializeBaseUrl(url: URL, isRelative: boolean): string {
|
|
159
|
+
return isRelative ? `${url.pathname}${url.search}` : url.toString();
|
|
160
|
+
}
|
|
161
|
+
|
|
135
162
|
/**
|
|
136
163
|
* Appends query string parameters to a URL. Existing URL parameters are kept,
|
|
137
164
|
* unless there is a conflict, in which case the new parameters override
|
|
@@ -141,7 +168,7 @@ function createURLWithParameters(
|
|
|
141
168
|
baseUrlString: string,
|
|
142
169
|
parameters: Record<string, unknown>
|
|
143
170
|
): string {
|
|
144
|
-
const baseUrl =
|
|
171
|
+
const {url: baseUrl, isRelative} = parseBaseUrl(baseUrlString);
|
|
145
172
|
for (const [key, value] of Object.entries(parameters)) {
|
|
146
173
|
if (isPureObject(value) || Array.isArray(value)) {
|
|
147
174
|
baseUrl.searchParams.set(key, JSON.stringify(value));
|
|
@@ -154,20 +181,20 @@ function createURLWithParameters(
|
|
|
154
181
|
}
|
|
155
182
|
}
|
|
156
183
|
}
|
|
157
|
-
return baseUrl
|
|
184
|
+
return serializeBaseUrl(baseUrl, isRelative);
|
|
158
185
|
}
|
|
159
186
|
|
|
160
187
|
/**
|
|
161
188
|
* Deletes query string parameters from a URL.
|
|
162
189
|
*/
|
|
163
190
|
function excludeURLParameters(baseUrlString: string, parameters: string[]) {
|
|
164
|
-
const baseUrl =
|
|
191
|
+
const {url: baseUrl, isRelative} = parseBaseUrl(baseUrlString);
|
|
165
192
|
for (const param of parameters) {
|
|
166
193
|
if (baseUrl.searchParams.has(param)) {
|
|
167
194
|
baseUrl.searchParams.delete(param);
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
|
-
return baseUrl
|
|
197
|
+
return serializeBaseUrl(baseUrl, isRelative);
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
/**
|
|
@@ -754,7 +754,13 @@ function createChannelProps(
|
|
|
754
754
|
};
|
|
755
755
|
}
|
|
756
756
|
|
|
757
|
-
function createLoadOptions(accessToken
|
|
757
|
+
function createLoadOptions(accessToken?: string) {
|
|
758
|
+
// No token (e.g. a source created with authMode: 'session', where tile URLs
|
|
759
|
+
// are same-origin and authenticated by a session cookie): attach no
|
|
760
|
+
// Authorization header rather than a literal 'Bearer undefined'.
|
|
761
|
+
if (!accessToken) {
|
|
762
|
+
return {loadOptions: {fetch: {credentials: 'same-origin'}}};
|
|
763
|
+
}
|
|
758
764
|
return {
|
|
759
765
|
loadOptions: {fetch: {headers: {Authorization: `Bearer ${accessToken}`}}},
|
|
760
766
|
};
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,11 @@ export * from './types.js';
|
|
|
18
18
|
export {
|
|
19
19
|
type APIErrorContext,
|
|
20
20
|
type APIRequestType,
|
|
21
|
+
type AuthMode,
|
|
22
|
+
type AuthOptions,
|
|
23
|
+
buildAuthHeaders,
|
|
24
|
+
getAuthCredentials,
|
|
25
|
+
rewriteUrlForSessionMode,
|
|
21
26
|
CartoAPIError,
|
|
22
27
|
type QueryOptions,
|
|
23
28
|
buildPublicMapUrl, // Internal, but required for fetchMap().
|
|
@@ -28,6 +33,7 @@ export {
|
|
|
28
33
|
} from './api/index.js';
|
|
29
34
|
|
|
30
35
|
export {_getHexagonResolution} from './spatial-index.js';
|
|
36
|
+
export {getPointsAggregationLevel as _getPointsAggregationLevel} from './spatial-index.js';
|
|
31
37
|
|
|
32
38
|
// For unit testing only.
|
|
33
39
|
export * from './filters/index.js';
|
package/src/models/common.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import {InvalidColumnError} from '../utils.js';
|
|
2
|
+
import {
|
|
3
|
+
buildAuthHeaders,
|
|
4
|
+
getAuthCredentials,
|
|
5
|
+
type AuthMode,
|
|
6
|
+
} from '../api/auth.js';
|
|
2
7
|
|
|
3
8
|
/** @privateRemarks Source: @carto/react-api */
|
|
4
9
|
export interface ModelRequestOptions {
|
|
@@ -55,10 +60,12 @@ export function dealWithApiError({
|
|
|
55
60
|
export async function makeCall({
|
|
56
61
|
url,
|
|
57
62
|
accessToken,
|
|
63
|
+
authMode,
|
|
58
64
|
opts,
|
|
59
65
|
}: {
|
|
60
66
|
url: string;
|
|
61
|
-
accessToken
|
|
67
|
+
accessToken?: string;
|
|
68
|
+
authMode?: AuthMode;
|
|
62
69
|
opts: ModelRequestOptions;
|
|
63
70
|
}) {
|
|
64
71
|
let response;
|
|
@@ -67,10 +74,13 @@ export async function makeCall({
|
|
|
67
74
|
try {
|
|
68
75
|
response = await fetch(url.toString(), {
|
|
69
76
|
headers: {
|
|
70
|
-
|
|
77
|
+
...buildAuthHeaders({accessToken, authMode}),
|
|
71
78
|
...(isPost && {'Content-Type': 'application/json'}),
|
|
72
79
|
...opts.headers,
|
|
73
80
|
},
|
|
81
|
+
...(getAuthCredentials(authMode) && {
|
|
82
|
+
credentials: getAuthCredentials(authMode),
|
|
83
|
+
}),
|
|
74
84
|
...(isPost && {
|
|
75
85
|
method: opts?.method,
|
|
76
86
|
body: opts?.body,
|
package/src/models/model.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {DEFAULT_GEO_COLUMN} from '../constants-internal.js';
|
|
2
|
+
import type {AuthMode} from '../api/auth.js';
|
|
2
3
|
import type {
|
|
3
4
|
Filter,
|
|
4
5
|
FilterLogicalOperator,
|
|
@@ -33,7 +34,8 @@ export interface ModelSource {
|
|
|
33
34
|
type: MapType;
|
|
34
35
|
apiVersion: ApiVersion;
|
|
35
36
|
apiBaseUrl: string;
|
|
36
|
-
accessToken
|
|
37
|
+
accessToken?: string;
|
|
38
|
+
authMode?: AuthMode;
|
|
37
39
|
clientId: string;
|
|
38
40
|
connectionName: string;
|
|
39
41
|
data: string;
|
|
@@ -72,11 +74,12 @@ export function executeModel(props: {
|
|
|
72
74
|
);
|
|
73
75
|
|
|
74
76
|
const {model, source, params, opts} = props;
|
|
75
|
-
const {type, apiVersion, apiBaseUrl,
|
|
76
|
-
source;
|
|
77
|
+
const {type, apiVersion, apiBaseUrl, connectionName, clientId} = source;
|
|
77
78
|
|
|
78
79
|
assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
|
|
79
|
-
|
|
80
|
+
// Auth (token vs session mode) is validated centrally by buildAuthHeaders,
|
|
81
|
+
// invoked from makeCall below — no separate accessToken assertion here, so
|
|
82
|
+
// that session mode (no accessToken) is not rejected before the request.
|
|
80
83
|
assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
|
|
81
84
|
assert(type !== 'tileset', 'executeModel: Tilesets not supported');
|
|
82
85
|
|
|
@@ -123,6 +126,7 @@ export function executeModel(props: {
|
|
|
123
126
|
return makeCall({
|
|
124
127
|
url,
|
|
125
128
|
accessToken: source.accessToken,
|
|
129
|
+
authMode: source.authMode,
|
|
126
130
|
opts: {
|
|
127
131
|
...opts,
|
|
128
132
|
method: isGet ? 'GET' : 'POST',
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
import {DEFAULT_API_BASE_URL} from '../constants.js';
|
|
6
6
|
import {DEFAULT_MAX_LENGTH_URL} from '../constants-internal.js';
|
|
7
7
|
import {buildSourceUrl} from '../api/endpoints.js';
|
|
8
|
+
import {
|
|
9
|
+
buildAuthHeaders,
|
|
10
|
+
getAuthCredentials,
|
|
11
|
+
rewriteUrlForSessionMode,
|
|
12
|
+
} from '../api/auth.js';
|
|
8
13
|
import {requestWithParameters} from '../api/request-with-parameters.js';
|
|
9
14
|
import type {
|
|
10
15
|
SourceOptionalOptions,
|
|
@@ -43,10 +48,12 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
|
|
|
43
48
|
}
|
|
44
49
|
const baseUrl = buildSourceUrl(mergedOptions);
|
|
45
50
|
const {clientId, maxLengthURL, localCache} = mergedOptions;
|
|
51
|
+
const sessionMode = options.authMode === 'session';
|
|
46
52
|
const headers = {
|
|
47
|
-
|
|
53
|
+
...buildAuthHeaders(options),
|
|
48
54
|
...options.headers,
|
|
49
55
|
};
|
|
56
|
+
const credentials = getAuthCredentials(options.authMode);
|
|
50
57
|
const parameters = {client: clientId, ...options.tags, ...urlParameters};
|
|
51
58
|
|
|
52
59
|
const errorContext: APIErrorContext = {
|
|
@@ -63,15 +70,22 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
|
|
|
63
70
|
errorContext,
|
|
64
71
|
maxLengthURL,
|
|
65
72
|
localCache,
|
|
73
|
+
credentials,
|
|
66
74
|
});
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
let dataUrl = tilejson.url[0];
|
|
69
77
|
if (cache) {
|
|
70
78
|
cache.value = parseInt(
|
|
71
79
|
new URL(dataUrl).searchParams.get('cache') || '',
|
|
72
80
|
10
|
|
73
81
|
);
|
|
74
82
|
}
|
|
83
|
+
if (sessionMode) {
|
|
84
|
+
// The instantiation response points at the tenant API host, which the
|
|
85
|
+
// browser cannot reach directly in session mode — route it through the
|
|
86
|
+
// same-origin proxy behind apiBaseUrl.
|
|
87
|
+
dataUrl = rewriteUrlForSessionMode(dataUrl, mergedOptions.apiBaseUrl);
|
|
88
|
+
}
|
|
75
89
|
errorContext.requestType = 'Map data';
|
|
76
90
|
|
|
77
91
|
const json = await requestWithParameters<TilejsonResult>({
|
|
@@ -81,8 +95,17 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
|
|
|
81
95
|
errorContext,
|
|
82
96
|
maxLengthURL,
|
|
83
97
|
localCache,
|
|
98
|
+
credentials,
|
|
84
99
|
});
|
|
85
|
-
if (
|
|
100
|
+
if (sessionMode) {
|
|
101
|
+
// Tile URL templates also point at the tenant API host. Rewrite them onto
|
|
102
|
+
// the proxy, and deliberately leave `json.accessToken` unset: consumers
|
|
103
|
+
// (e.g. deck.gl tile layers) must not attach an Authorization header —
|
|
104
|
+
// the session credential rides on the same-origin cookie instead.
|
|
105
|
+
json.tiles = json.tiles?.map((template) =>
|
|
106
|
+
rewriteUrlForSessionMode(template, mergedOptions.apiBaseUrl)
|
|
107
|
+
);
|
|
108
|
+
} else if (accessToken) {
|
|
86
109
|
json.accessToken = accessToken;
|
|
87
110
|
}
|
|
88
111
|
if (schema) {
|
package/src/sources/types.ts
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import type {Filters, SchemaField, QueryParameters} from '../types.js';
|
|
6
|
+
import type {AuthOptions} from '../api/auth.js';
|
|
6
7
|
import type {RasterBandColorinterp} from './constants.js';
|
|
7
8
|
|
|
8
|
-
export type SourceRequiredOptions = {
|
|
9
|
-
/** Carto platform access token. */
|
|
10
|
-
accessToken: string;
|
|
11
|
-
|
|
9
|
+
export type SourceRequiredOptions = AuthOptions & {
|
|
12
10
|
/** Data warehouse connection name in Carto platform. */
|
|
13
11
|
connectionName: string;
|
|
14
12
|
};
|
|
@@ -466,7 +464,13 @@ export type RasterMetadata = {
|
|
|
466
464
|
};
|
|
467
465
|
|
|
468
466
|
export type TilejsonResult = Tilejson & {
|
|
469
|
-
|
|
467
|
+
/**
|
|
468
|
+
* Access token used to fetch the tiles. Absent when the source was created
|
|
469
|
+
* with `authMode: 'session'` — tile URLs are then rewritten onto the
|
|
470
|
+
* configured `apiBaseUrl` and authenticated by the server (session cookie),
|
|
471
|
+
* so consumers must not attach an Authorization header.
|
|
472
|
+
*/
|
|
473
|
+
accessToken?: string;
|
|
470
474
|
schema: SchemaField[];
|
|
471
475
|
};
|
|
472
476
|
|
package/src/spatial-index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type {TileResolution} from './sources/types.js';
|
|
2
|
+
|
|
1
3
|
// Default tile display size in deck.gl, in viewport pixels. May differ
|
|
2
4
|
// from size or resolution assumed when generating the tile data,
|
|
3
5
|
const DEFAULT_TILE_SIZE = 512;
|
|
@@ -30,3 +32,42 @@ export function _getHexagonResolution(
|
|
|
30
32
|
Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS)
|
|
31
33
|
);
|
|
32
34
|
}
|
|
35
|
+
|
|
36
|
+
// Default of the maps-api setting MAPS_API_V3_DYNAMIC_TILES_POINTS_AGGREGATION_LEVEL.
|
|
37
|
+
const DYNAMIC_TILES_POINTS_AGGREGATION_LEVEL = 8;
|
|
38
|
+
|
|
39
|
+
// Per-tileResolution correction, identical to the maps-api dynamic tiler.
|
|
40
|
+
const AGG_LEVEL_CORRECTION_BY_TILE_RESOLUTION: Record<TileResolution, number> =
|
|
41
|
+
{
|
|
42
|
+
0.25: -1,
|
|
43
|
+
0.5: 0,
|
|
44
|
+
1: 1,
|
|
45
|
+
2: 2,
|
|
46
|
+
4: 3,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Quadbin level at which the maps-api dynamic tiler implicitly aggregates a
|
|
51
|
+
* point source for a given tile zoom and resolution. Lets a client reproduce
|
|
52
|
+
* that level — and so the aggregation cell a point falls into — without an
|
|
53
|
+
* extra round-trip to the server.
|
|
54
|
+
*
|
|
55
|
+
* Ported verbatim from the maps-api dynamic tiler (`getPointsAggregationLevel`).
|
|
56
|
+
* The base offset mirrors the server default of
|
|
57
|
+
* `MAPS_API_V3_DYNAMIC_TILES_POINTS_AGGREGATION_LEVEL`; a deployment that
|
|
58
|
+
* overrides that env var drifts from this computation.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export function getPointsAggregationLevel({
|
|
62
|
+
tileResolution,
|
|
63
|
+
zoomLevel,
|
|
64
|
+
}: {
|
|
65
|
+
tileResolution: TileResolution;
|
|
66
|
+
zoomLevel: number;
|
|
67
|
+
}): number {
|
|
68
|
+
return (
|
|
69
|
+
zoomLevel +
|
|
70
|
+
DYNAMIC_TILES_POINTS_AGGREGATION_LEVEL +
|
|
71
|
+
AGG_LEVEL_CORRECTION_BY_TILE_RESOLUTION[tileResolution]
|
|
72
|
+
);
|
|
73
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -116,21 +116,13 @@ export type AggregationType =
|
|
|
116
116
|
export type GeometrySpatialFilter = Polygon | MultiPolygon;
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Filter restricting a widget query to a
|
|
120
|
-
*
|
|
121
|
-
* the server-side model API only; maps-api chooses the SQL strategy from
|
|
122
|
-
* `spatialDataType`, so the same payload works for both natively-indexed
|
|
123
|
-
* sources and point/geometry sources dynamically aggregated into cells.
|
|
124
|
-
*
|
|
125
|
-
* @privateRemarks Source: @carto/query-builder (SpatialIndexFilter)
|
|
119
|
+
* Filter restricting a widget query to a selection of spatial-index cells,
|
|
120
|
+
* e.g. from point-and-click on an H3 or Quadbin layer.
|
|
126
121
|
*/
|
|
127
122
|
export interface SpatialIndexFilter {
|
|
128
|
-
/**
|
|
129
|
-
* Selected cell ids. h3 = hex string; h3int = `BigInt(\`0x${id}\`)`; quadbin
|
|
130
|
-
* = decimal or `0x`-hex string.
|
|
131
|
-
*/
|
|
123
|
+
/** Selected cell ids. */
|
|
132
124
|
indexes: string[];
|
|
133
|
-
/**
|
|
125
|
+
/** Spatial index type of the filtered column. */
|
|
134
126
|
type: 'h3' | 'h3int' | 'quadbin';
|
|
135
127
|
}
|
|
136
128
|
|
|
@@ -141,7 +133,14 @@ export type SpatialFilter = GeometrySpatialFilter | SpatialIndexFilter;
|
|
|
141
133
|
export function isSpatialIndexFilter(
|
|
142
134
|
spatialFilter: SpatialFilter
|
|
143
135
|
): spatialFilter is SpatialIndexFilter {
|
|
144
|
-
|
|
136
|
+
const filter = spatialFilter as
|
|
137
|
+
| Partial<SpatialIndexFilter>
|
|
138
|
+
| null
|
|
139
|
+
| undefined;
|
|
140
|
+
return (
|
|
141
|
+
Array.isArray(filter?.indexes) &&
|
|
142
|
+
['h3', 'h3int', 'quadbin'].includes(filter?.type as string)
|
|
143
|
+
);
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
/** @privateRemarks Source: @deck.gl/carto */
|
|
@@ -30,6 +30,7 @@ import {AggregationTypes, ApiVersion} from '../constants.js';
|
|
|
30
30
|
import {getApplicableFilters} from '../filters.js';
|
|
31
31
|
import {OTHERS_CATEGORY_NAME} from './constants.js';
|
|
32
32
|
import {requestWithParameters} from '../api/request-with-parameters.js';
|
|
33
|
+
import {buildAuthHeaders, getAuthCredentials} from '../api/auth.js';
|
|
33
34
|
import type {APIErrorContext} from '../api/carto-api-error.js';
|
|
34
35
|
|
|
35
36
|
export type WidgetRemoteSourceProps = WidgetSourceProps;
|
|
@@ -88,6 +89,7 @@ export abstract class WidgetRemoteSource<
|
|
|
88
89
|
apiBaseUrl: props.apiBaseUrl as string,
|
|
89
90
|
clientId: props.clientId as string,
|
|
90
91
|
accessToken: props.accessToken,
|
|
92
|
+
authMode: props.authMode,
|
|
91
93
|
connectionName: props.connectionName,
|
|
92
94
|
filters: getApplicableFilters(filterOwner, filters || props.filters),
|
|
93
95
|
filtersLogicalOperator: props.filtersLogicalOperator,
|
|
@@ -486,7 +488,7 @@ export abstract class WidgetRemoteSource<
|
|
|
486
488
|
}
|
|
487
489
|
|
|
488
490
|
const headers = {
|
|
489
|
-
|
|
491
|
+
...buildAuthHeaders(this.props),
|
|
490
492
|
...this.props.headers,
|
|
491
493
|
};
|
|
492
494
|
|
|
@@ -511,6 +513,7 @@ export abstract class WidgetRemoteSource<
|
|
|
511
513
|
signal,
|
|
512
514
|
errorContext,
|
|
513
515
|
parameters,
|
|
516
|
+
credentials: getAuthCredentials(this.props.authMode),
|
|
514
517
|
}).then(({extent: {xmin, ymin, xmax, ymax}}) => ({
|
|
515
518
|
bbox: [xmin, ymin, xmax, ymax],
|
|
516
519
|
}));
|