2021-09-05 11:16:19 -07:00
|
|
|
/**
|
|
|
|
* API: HTTP client and utilities.
|
|
|
|
* @see {@link https://github.com/axios/axios}
|
|
|
|
* @module soapbox/api
|
|
|
|
*/
|
2020-03-27 13:59:38 -07:00
|
|
|
'use strict';
|
|
|
|
|
2022-03-14 16:06:42 -07:00
|
|
|
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
2020-03-27 13:59:38 -07:00
|
|
|
import LinkHeader from 'http-link-header';
|
2021-08-21 18:41:29 -07:00
|
|
|
import { createSelector } from 'reselect';
|
2022-01-10 14:25:06 -08:00
|
|
|
|
2022-11-15 12:48:54 -08:00
|
|
|
import * as BuildConfig from 'soapbox/build-config';
|
2023-07-20 13:03:23 -07:00
|
|
|
import { selectAccount } from 'soapbox/selectors';
|
2022-03-14 16:06:42 -07:00
|
|
|
import { RootState } from 'soapbox/store';
|
2022-06-07 13:21:18 -07:00
|
|
|
import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth';
|
2020-03-27 13:59:38 -07:00
|
|
|
|
2022-06-21 11:14:45 -07:00
|
|
|
import type MockAdapter from 'axios-mock-adapter';
|
|
|
|
|
2021-09-05 11:16:19 -07:00
|
|
|
/**
|
2022-11-15 05:43:26 -08:00
|
|
|
Parse Link headers, mostly for pagination.
|
|
|
|
@see {@link https://www.npmjs.com/package/http-link-header}
|
|
|
|
@param {object} response - Axios response object
|
|
|
|
@returns {object} Link object
|
|
|
|
*/
|
2022-03-14 16:06:42 -07:00
|
|
|
export const getLinks = (response: AxiosResponse): LinkHeader => {
|
|
|
|
return new LinkHeader(response.headers?.link);
|
2020-03-27 13:59:38 -07:00
|
|
|
};
|
|
|
|
|
2022-04-23 20:31:49 -07:00
|
|
|
export const getNextLink = (response: AxiosResponse): string | undefined => {
|
|
|
|
return getLinks(response).refs.find(link => link.rel === 'next')?.uri;
|
|
|
|
};
|
|
|
|
|
2022-12-04 15:53:56 -08:00
|
|
|
export const getPrevLink = (response: AxiosResponse): string | undefined => {
|
|
|
|
return getLinks(response).refs.find(link => link.rel === 'prev')?.uri;
|
|
|
|
};
|
|
|
|
|
2022-03-14 16:06:42 -07:00
|
|
|
const getToken = (state: RootState, authType: string) => {
|
2021-03-31 12:47:54 -07:00
|
|
|
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
|
2021-03-23 17:06:55 -07:00
|
|
|
};
|
2020-04-29 12:06:26 -07:00
|
|
|
|
2022-03-14 16:06:42 -07:00
|
|
|
const maybeParseJSON = (data: string) => {
|
2021-03-31 12:47:54 -07:00
|
|
|
try {
|
|
|
|
return JSON.parse(data);
|
2022-04-11 12:58:48 -07:00
|
|
|
} catch (Exception) {
|
2021-03-31 12:47:54 -07:00
|
|
|
return data;
|
|
|
|
}
|
|
|
|
};
|
2020-04-29 12:06:26 -07:00
|
|
|
|
2021-08-21 18:41:29 -07:00
|
|
|
const getAuthBaseURL = createSelector([
|
2023-07-20 13:03:23 -07:00
|
|
|
(state: RootState, me: string | false | null) => me ? selectAccount(state, me)?.url : undefined,
|
2022-12-25 15:31:07 -08:00
|
|
|
(state: RootState, _me: string | false | null) => state.auth.me,
|
2021-08-21 18:41:29 -07:00
|
|
|
], (accountUrl, authUserUrl) => {
|
|
|
|
const baseURL = parseBaseURL(accountUrl) || parseBaseURL(authUserUrl);
|
|
|
|
return baseURL !== window.location.origin ? baseURL : '';
|
|
|
|
});
|
|
|
|
|
2021-09-05 11:16:19 -07:00
|
|
|
/**
|
2022-11-15 05:43:26 -08:00
|
|
|
* Base client for HTTP requests.
|
|
|
|
* @param {string} accessToken
|
|
|
|
* @param {string} baseURL
|
|
|
|
* @returns {object} Axios instance
|
|
|
|
*/
|
2023-08-28 14:28:51 -07:00
|
|
|
export const baseClient = (
|
|
|
|
accessToken?: string | null,
|
|
|
|
baseURL: string = '',
|
|
|
|
nostrSign = false,
|
|
|
|
): AxiosInstance => {
|
|
|
|
const headers: Record<string, string> = {};
|
|
|
|
|
|
|
|
if (accessToken) {
|
|
|
|
headers.Authorization = `Bearer ${accessToken}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nostrSign) {
|
|
|
|
headers['X-Nostr-Sign'] = 'true';
|
|
|
|
}
|
|
|
|
|
2020-04-04 13:28:57 -07:00
|
|
|
return axios.create({
|
2021-08-26 20:39:47 -07:00
|
|
|
// When BACKEND_URL is set, always use it.
|
2022-03-14 16:06:42 -07:00
|
|
|
baseURL: isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL,
|
2023-08-28 14:28:51 -07:00
|
|
|
headers,
|
2021-03-31 12:47:54 -07:00
|
|
|
transformResponse: [maybeParseJSON],
|
2020-04-04 13:28:57 -07:00
|
|
|
});
|
|
|
|
};
|
2021-03-31 12:47:54 -07:00
|
|
|
|
2021-09-05 11:16:19 -07:00
|
|
|
/**
|
2022-11-15 05:43:26 -08:00
|
|
|
* Dumb client for grabbing static files.
|
|
|
|
* It uses FE_SUBDIRECTORY and parses JSON if possible.
|
|
|
|
* No authorization is needed.
|
|
|
|
*/
|
2021-09-05 11:16:19 -07:00
|
|
|
export const staticClient = axios.create({
|
2022-03-14 16:06:42 -07:00
|
|
|
baseURL: BuildConfig.FE_SUBDIRECTORY,
|
2021-09-05 11:16:19 -07:00
|
|
|
transformResponse: [maybeParseJSON],
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
2022-11-15 05:43:26 -08:00
|
|
|
* Stateful API client.
|
|
|
|
* Uses credentials from the Redux store if available.
|
|
|
|
* @param {function} getState - Must return the Redux state
|
|
|
|
* @param {string} authType - Either 'user' or 'app'
|
|
|
|
* @returns {object} Axios instance
|
|
|
|
*/
|
2022-03-14 16:06:42 -07:00
|
|
|
export default (getState: () => RootState, authType: string = 'user'): AxiosInstance => {
|
2021-08-21 18:41:29 -07:00
|
|
|
const state = getState();
|
|
|
|
const accessToken = getToken(state, authType);
|
2022-03-14 16:06:42 -07:00
|
|
|
const me = state.me;
|
2022-04-04 12:24:42 -07:00
|
|
|
const baseURL = me ? getAuthBaseURL(state, me) : '';
|
2021-08-21 18:41:29 -07:00
|
|
|
|
2023-08-28 14:28:51 -07:00
|
|
|
const relayUrl = state.getIn(['instance', 'nostr', 'relay']) as string | undefined;
|
|
|
|
const pubkey = state.getIn(['instance', 'nostr', 'pubkey']) as string | undefined;
|
|
|
|
const nostrSign = Boolean(relayUrl && pubkey);
|
|
|
|
|
|
|
|
return baseClient(accessToken, baseURL, nostrSign);
|
2021-03-31 12:47:54 -07:00
|
|
|
};
|
2022-06-21 11:14:45 -07:00
|
|
|
|
|
|
|
// The Jest mock exports these, so they're needed for TypeScript.
|
|
|
|
export const __stub = (_func: (mock: MockAdapter) => void) => 0;
|
|
|
|
export const __clear = (): Function[] => [];
|