| /** |
| * 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 { |
| ClientConfig, |
| ClientTimeout, |
| Credentials, |
| CsrfPromise, |
| CsrfToken, |
| FetchRetryOptions, |
| Headers, |
| Host, |
| Mode, |
| Protocol, |
| RequestConfig, |
| ParseMethod, |
| } from './types'; |
| import { DEFAULT_FETCH_RETRY_OPTIONS, DEFAULT_BASE_URL } from './constants'; |
| |
| export default class SupersetClientClass { |
| credentials: Credentials; |
| |
| csrfToken?: CsrfToken; |
| |
| csrfPromise?: CsrfPromise; |
| |
| fetchRetryOptions?: FetchRetryOptions; |
| |
| baseUrl: string; |
| |
| protocol: Protocol; |
| |
| host: Host; |
| |
| headers: Headers; |
| |
| mode: Mode; |
| |
| timeout: ClientTimeout; |
| |
| constructor({ |
| baseUrl = DEFAULT_BASE_URL, |
| host, |
| protocol, |
| headers = {}, |
| fetchRetryOptions = {}, |
| mode = 'same-origin', |
| timeout, |
| credentials = undefined, |
| csrfToken = undefined, |
| }: ClientConfig = {}) { |
| 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 as Protocol; |
| this.headers = { ...headers }; |
| this.mode = mode; |
| this.timeout = timeout; |
| this.credentials = credentials; |
| this.csrfToken = csrfToken; |
| 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); |
| } |
| } |
| |
| async init(force = false): CsrfPromise { |
| if (this.isAuthenticated() && !force) { |
| return this.csrfPromise as CsrfPromise; |
| } |
| return this.getCSRFToken(); |
| } |
| |
| async reAuthenticate() { |
| return this.init(true); |
| } |
| |
| isAuthenticated(): boolean { |
| // if CSRF protection is disabled in the Superset app, the token may be an empty string |
| return this.csrfToken !== null && this.csrfToken !== undefined; |
| } |
| |
| async get<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) { |
| return this.request({ ...requestConfig, method: 'GET' }); |
| } |
| |
| async delete<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) { |
| return this.request({ ...requestConfig, method: 'DELETE' }); |
| } |
| |
| async put<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) { |
| return this.request({ ...requestConfig, method: 'PUT' }); |
| } |
| |
| async post<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) { |
| return this.request({ ...requestConfig, method: 'POST' }); |
| } |
| |
| async request<T extends ParseMethod = 'json'>({ |
| credentials, |
| mode, |
| endpoint, |
| host, |
| url, |
| headers, |
| timeout, |
| fetchRetryOptions, |
| ...rest |
| }: RequestConfig & { parseMethod?: T }) { |
| 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, |
| }); |
| } |
| |
| async ensureAuth(): CsrfPromise { |
| 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: 'superset/csrf_token/' }), |
| parseMethod: 'json', |
| }).then(({ json }) => { |
| if (typeof json === 'object') { |
| this.csrfToken = json.csrf_token as string; |
| 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, |
| }: { |
| endpoint?: string; |
| host?: Host; |
| url?: string; |
| } = {}) { |
| 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}`; |
| } |
| } |