Merge remote-tracking branch 'origin/develop' into chats
This commit is contained in:
39
app/soapbox/api/__mocks__/index.ts
Normal file
39
app/soapbox/api/__mocks__/index.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import LinkHeader from 'http-link-header';
|
||||
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
const api = jest.requireActual('../index') as Record<string, Function>;
|
||||
let mocks: Array<Function> = [];
|
||||
|
||||
export const __stub = (func: (mock: MockAdapter) => void) => mocks.push(func);
|
||||
export const __clear = (): Function[] => mocks = [];
|
||||
|
||||
const setupMock = (axios: AxiosInstance) => {
|
||||
const mock = new MockAdapter(axios, { onNoMatch: 'throwException' });
|
||||
mocks.map(func => func(mock));
|
||||
};
|
||||
|
||||
export const staticClient = api.staticClient;
|
||||
|
||||
export const getLinks = (response: AxiosResponse): LinkHeader => {
|
||||
return new LinkHeader(response.headers?.link);
|
||||
};
|
||||
|
||||
export const getNextLink = (response: AxiosResponse) => {
|
||||
const nextLink = new LinkHeader(response.headers?.link);
|
||||
return nextLink.refs.find((ref) => ref.uri)?.uri;
|
||||
};
|
||||
|
||||
export const baseClient = (...params: any[]) => {
|
||||
const axios = api.baseClient(...params);
|
||||
setupMock(axios);
|
||||
return axios;
|
||||
};
|
||||
|
||||
export default (...params: any[]) => {
|
||||
const axios = api.default(...params);
|
||||
setupMock(axios);
|
||||
return axios;
|
||||
};
|
||||
98
app/soapbox/api/index.ts
Normal file
98
app/soapbox/api/index.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* API: HTTP client and utilities.
|
||||
* @see {@link https://github.com/axios/axios}
|
||||
* @module soapbox/api
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import LinkHeader from 'http-link-header';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import * as BuildConfig from 'soapbox/build-config';
|
||||
import { RootState } from 'soapbox/store';
|
||||
import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth';
|
||||
|
||||
import type MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
export const getLinks = (response: AxiosResponse): LinkHeader => {
|
||||
return new LinkHeader(response.headers?.link);
|
||||
};
|
||||
|
||||
export const getNextLink = (response: AxiosResponse): string | undefined => {
|
||||
return getLinks(response).refs.find(link => link.rel === 'next')?.uri;
|
||||
};
|
||||
|
||||
const getToken = (state: RootState, authType: string) => {
|
||||
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
|
||||
};
|
||||
|
||||
const maybeParseJSON = (data: string) => {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (Exception) {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const getAuthBaseURL = createSelector([
|
||||
(state: RootState, me: string | false | null) => state.accounts.getIn([me, 'url']),
|
||||
(state: RootState, _me: string | false | null) => state.auth.get('me'),
|
||||
], (accountUrl, authUserUrl) => {
|
||||
const baseURL = parseBaseURL(accountUrl) || parseBaseURL(authUserUrl);
|
||||
return baseURL !== window.location.origin ? baseURL : '';
|
||||
});
|
||||
|
||||
/**
|
||||
* Base client for HTTP requests.
|
||||
* @param {string} accessToken
|
||||
* @param {string} baseURL
|
||||
* @returns {object} Axios instance
|
||||
*/
|
||||
export const baseClient = (accessToken?: string | null, baseURL: string = ''): AxiosInstance => {
|
||||
return axios.create({
|
||||
// When BACKEND_URL is set, always use it.
|
||||
baseURL: isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL,
|
||||
headers: Object.assign(accessToken ? {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
} : {}),
|
||||
|
||||
transformResponse: [maybeParseJSON],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dumb client for grabbing static files.
|
||||
* It uses FE_SUBDIRECTORY and parses JSON if possible.
|
||||
* No authorization is needed.
|
||||
*/
|
||||
export const staticClient = axios.create({
|
||||
baseURL: BuildConfig.FE_SUBDIRECTORY,
|
||||
transformResponse: [maybeParseJSON],
|
||||
});
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export default (getState: () => RootState, authType: string = 'user'): AxiosInstance => {
|
||||
const state = getState();
|
||||
const accessToken = getToken(state, authType);
|
||||
const me = state.me;
|
||||
const baseURL = me ? getAuthBaseURL(state, me) : '';
|
||||
|
||||
return baseClient(accessToken, baseURL);
|
||||
};
|
||||
|
||||
// The Jest mock exports these, so they're needed for TypeScript.
|
||||
export const __stub = (_func: (mock: MockAdapter) => void) => 0;
|
||||
export const __clear = (): Function[] => [];
|
||||
Reference in New Issue
Block a user