@superset-ui/core
Version:
187 lines • 7.6 kB
JavaScript
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import callApiAndParseWithTimeout from './callApi/callApiAndParseWithTimeout';
import { DEFAULT_FETCH_RETRY_OPTIONS, DEFAULT_BASE_URL } from './constants';
const defaultUnauthorizedHandler = () => {
if (!window.location.pathname.startsWith('/login')) {
window.location.href = `/login?next=${window.location.href}`;
}
};
export default class SupersetClientClass {
credentials;
csrfToken;
csrfPromise;
guestToken;
guestTokenHeaderName;
fetchRetryOptions;
baseUrl;
protocol;
host;
headers;
mode;
timeout;
handleUnauthorized;
constructor({ baseUrl = DEFAULT_BASE_URL, host, protocol, headers = {}, fetchRetryOptions = {}, mode = 'same-origin', timeout, credentials = undefined, csrfToken = undefined, guestToken = undefined, guestTokenHeaderName = 'X-GuestToken', unauthorizedHandler = defaultUnauthorizedHandler, } = {}) {
const url = new URL(host || protocol
? `${protocol || 'https:'}//${host || 'localhost'}`
: baseUrl,
// baseUrl for API could also be relative, so we provide current location.href
// as the base of baseUrl
window.location.href);
this.baseUrl = url.href.replace(/\/+$/, ''); // always strip trailing slash
this.host = url.host;
this.protocol = url.protocol;
this.headers = { Accept: 'application/json', ...headers }; // defaulting accept to json
this.mode = mode;
this.timeout = timeout;
this.credentials = credentials;
this.csrfToken = csrfToken;
this.guestToken = guestToken;
this.guestTokenHeaderName = guestTokenHeaderName;
this.fetchRetryOptions = {
...DEFAULT_FETCH_RETRY_OPTIONS,
...fetchRetryOptions,
};
if (typeof this.csrfToken === 'string') {
this.headers = { ...this.headers, 'X-CSRFToken': this.csrfToken };
this.csrfPromise = Promise.resolve(this.csrfToken);
}
if (guestToken) {
this.headers[guestTokenHeaderName] = guestToken;
}
this.handleUnauthorized = unauthorizedHandler;
}
async init(force = false) {
if (this.isAuthenticated() && !force) {
return this.csrfPromise;
}
return this.getCSRFToken();
}
async postForm(url, payload, target = '_blank') {
if (url) {
await this.ensureAuth();
const hiddenForm = document.createElement('form');
hiddenForm.action = url;
hiddenForm.method = 'POST';
hiddenForm.target = target;
const payloadWithToken = {
...payload,
csrf_token: this.csrfToken,
};
if (this.guestToken) {
payloadWithToken.guest_token = this.guestToken;
}
Object.entries(payloadWithToken).forEach(([key, value]) => {
const data = document.createElement('input');
data.type = 'hidden';
data.name = key;
data.value = value;
hiddenForm.appendChild(data);
});
document.body.appendChild(hiddenForm);
hiddenForm.submit();
document.body.removeChild(hiddenForm);
}
}
async reAuthenticate() {
return this.init(true);
}
isAuthenticated() {
// if CSRF protection is disabled in the Superset app, the token may be an empty string
return this.csrfToken !== null && this.csrfToken !== undefined;
}
getGuestToken() {
return this.guestToken;
}
async get(requestConfig) {
return this.request({ ...requestConfig, method: 'GET' });
}
async delete(requestConfig) {
return this.request({ ...requestConfig, method: 'DELETE' });
}
async put(requestConfig) {
return this.request({ ...requestConfig, method: 'PUT' });
}
async post(requestConfig) {
return this.request({ ...requestConfig, method: 'POST' });
}
async request({ credentials, mode, endpoint, host, url, headers, timeout, fetchRetryOptions, ignoreUnauthorized = false, ...rest }) {
await this.ensureAuth();
return callApiAndParseWithTimeout({
...rest,
credentials: credentials ?? this.credentials,
mode: mode ?? this.mode,
url: this.getUrl({ endpoint, host, url }),
headers: { ...this.headers, ...headers },
timeout: timeout ?? this.timeout,
fetchRetryOptions: fetchRetryOptions ?? this.fetchRetryOptions,
}).catch(res => {
if (res?.status === 401 && !ignoreUnauthorized) {
this.handleUnauthorized();
}
return Promise.reject(res);
});
}
async ensureAuth() {
return (this.csrfPromise ??
// eslint-disable-next-line prefer-promise-reject-errors
Promise.reject({
error: `SupersetClient has not been provided a CSRF token, ensure it is
initialized with \`client.getCSRFToken()\` or try logging in at
${this.getUrl({ endpoint: '/login' })}`,
}));
}
async getCSRFToken() {
this.csrfToken = undefined;
// If we can request this resource successfully, it means that the user has
// authenticated. If not we throw an error prompting to authenticate.
this.csrfPromise = callApiAndParseWithTimeout({
credentials: this.credentials,
headers: {
...this.headers,
},
method: 'GET',
mode: this.mode,
timeout: this.timeout,
url: this.getUrl({ endpoint: 'api/v1/security/csrf_token/' }),
parseMethod: 'json',
}).then(({ json }) => {
if (typeof json === 'object') {
this.csrfToken = json.result;
if (typeof this.csrfToken === 'string') {
this.headers = { ...this.headers, 'X-CSRFToken': this.csrfToken };
}
}
if (this.isAuthenticated()) {
return this.csrfToken;
}
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject({ error: 'Failed to fetch CSRF token' });
});
return this.csrfPromise;
}
getUrl({ host: inputHost, endpoint = '', url, } = {}) {
if (typeof url === 'string')
return url;
const host = inputHost ?? this.host;
const cleanHost = host.slice(-1) === '/' ? host.slice(0, -1) : host; // no backslash
return `${this.protocol}//${cleanHost}/${endpoint[0] === '/' ? endpoint.slice(1) : endpoint}`;
}
}
//# sourceMappingURL=SupersetClientClass.js.map