Merge branch 'ts' into 'develop'

TypeScript: ChatPanes, CryptoDonate, utils/ethereum

See merge request soapbox-pub/soapbox-fe!1521
environments/review-develop-3zknud/deployments/246
marcin mikołajczak 2 years ago
commit 615ff427e4

@ -8,6 +8,8 @@
import { baseClient } from '../api';
import type { AnyAction } from 'redux';
export const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST';
export const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS';
export const APP_CREATE_FAIL = 'APP_CREATE_FAIL';
@ -16,12 +18,12 @@ export const APP_VERIFY_CREDENTIALS_REQUEST = 'APP_VERIFY_CREDENTIALS_REQUEST';
export const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS';
export const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL';
export function createApp(params, baseURL) {
return (dispatch, getState) => {
export function createApp(params?: Record<string, string>, baseURL?: string) {
return (dispatch: React.Dispatch<AnyAction>) => {
dispatch({ type: APP_CREATE_REQUEST, params });
return baseClient(null, baseURL).post('/api/v1/apps', params).then(({ data: app }) => {
dispatch({ type: APP_CREATE_SUCCESS, params, app });
return app;
return app as Record<string, string>;
}).catch(error => {
dispatch({ type: APP_CREATE_FAIL, params, error });
throw error;
@ -29,8 +31,8 @@ export function createApp(params, baseURL) {
};
}
export function verifyAppCredentials(token) {
return (dispatch, getState) => {
export function verifyAppCredentials(token: string) {
return (dispatch: React.Dispatch<AnyAction>) => {
dispatch({ type: APP_VERIFY_CREDENTIALS_REQUEST, token });
return baseClient(token).get('/api/v1/apps/verify_credentials').then(({ data: app }) => {
dispatch({ type: APP_VERIFY_CREDENTIALS_SUCCESS, token, app });

@ -26,6 +26,10 @@ import api, { baseClient } from '../api';
import { importFetchedAccount } from './importer';
import type { AxiosError } from 'axios';
import type { Map as ImmutableMap } from 'immutable';
import type { AppDispatch, RootState } from 'soapbox/store';
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
@ -48,35 +52,32 @@ export const messages = defineMessages({
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
});
const noOp = () => new Promise(f => f());
const noOp = () => new Promise(f => f(undefined));
const getScopes = state => {
const instance = state.get('instance');
const getScopes = (state: RootState) => {
const instance = state.instance;
const { scopes } = getFeatures(instance);
return scopes;
};
function createAppAndToken() {
return (dispatch, getState) => {
return dispatch(getAuthApp()).then(() => {
return dispatch(createAppToken());
});
};
}
const createAppAndToken = () =>
(dispatch: AppDispatch) =>
dispatch(getAuthApp()).then(() =>
dispatch(createAppToken()),
);
/** Create an auth app, or use it from build config */
function getAuthApp() {
return (dispatch, getState) => {
const getAuthApp = () =>
(dispatch: AppDispatch) => {
if (customApp?.client_secret) {
return noOp().then(() => dispatch({ type: AUTH_APP_CREATED, app: customApp }));
} else {
return dispatch(createAuthApp());
}
};
}
function createAuthApp() {
return (dispatch, getState) => {
const createAuthApp = () =>
(dispatch: AppDispatch, getState: () => any) => {
const params = {
client_name: sourceCode.displayName,
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
@ -84,15 +85,14 @@ function createAuthApp() {
website: sourceCode.homepage,
};
return dispatch(createApp(params)).then(app => {
return dispatch({ type: AUTH_APP_CREATED, app });
});
return dispatch(createApp(params)).then((app: Record<string, string>) =>
dispatch({ type: AUTH_APP_CREATED, app }),
);
};
}
function createAppToken() {
return (dispatch, getState) => {
const app = getState().getIn(['auth', 'app']);
const createAppToken = () =>
(dispatch: AppDispatch, getState: () => any) => {
const app = getState().auth.get('app');
const params = {
client_id: app.get('client_id'),
@ -102,15 +102,14 @@ function createAppToken() {
scope: getScopes(getState()),
};
return dispatch(obtainOAuthToken(params)).then(token => {
return dispatch({ type: AUTH_APP_AUTHORIZED, app, token });
});
return dispatch(obtainOAuthToken(params)).then((token: Record<string, string | number>) =>
dispatch({ type: AUTH_APP_AUTHORIZED, app, token }),
);
};
}
function createUserToken(username, password) {
return (dispatch, getState) => {
const app = getState().getIn(['auth', 'app']);
const createUserToken = (username: string, password: string) =>
(dispatch: AppDispatch, getState: () => any) => {
const app = getState().auth.get('app');
const params = {
client_id: app.get('client_id'),
@ -123,14 +122,13 @@ function createUserToken(username, password) {
};
return dispatch(obtainOAuthToken(params))
.then(token => dispatch(authLoggedIn(token)));
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)));
};
}
export function refreshUserToken() {
return (dispatch, getState) => {
const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']);
const app = getState().getIn(['auth', 'app']);
export const refreshUserToken = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const refreshToken = getState().auth.getIn(['user', 'refresh_token']);
const app = getState().auth.get('app');
if (!refreshToken) return dispatch(noOp);
@ -144,13 +142,12 @@ export function refreshUserToken() {
};
return dispatch(obtainOAuthToken(params))
.then(token => dispatch(authLoggedIn(token)));
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)));
};
}
export function otpVerify(code, mfa_token) {
return (dispatch, getState) => {
const app = getState().getIn(['auth', 'app']);
export const otpVerify = (code: string, mfa_token: string) =>
(dispatch: AppDispatch, getState: () => any) => {
const app = getState().auth.get('app');
return api(getState, 'app').post('/oauth/mfa/challenge', {
client_id: app.get('client_id'),
client_secret: app.get('client_secret'),
@ -161,18 +158,17 @@ export function otpVerify(code, mfa_token) {
scope: getScopes(getState()),
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
};
}
export function verifyCredentials(token, accountUrl) {
export const verifyCredentials = (token: string, accountUrl?: string) => {
const baseURL = parseBaseURL(accountUrl);
return (dispatch, getState) => {
return (dispatch: AppDispatch, getState: () => any) => {
dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token });
return baseClient(token, baseURL).get('/api/v1/accounts/verify_credentials').then(({ data: account }) => {
dispatch(importFetchedAccount(account));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;
}).catch(error => {
if (error?.response?.status === 403 && error?.response?.data?.id) {
@ -180,75 +176,64 @@ export function verifyCredentials(token, accountUrl) {
const account = error.response.data;
dispatch(importFetchedAccount(account));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;
} else {
if (getState().get('me') === null) dispatch(fetchMeFail(error));
if (getState().me === null) dispatch(fetchMeFail(error));
dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true });
return error;
}
});
};
}
};
export function rememberAuthAccount(accountUrl) {
return (dispatch, getState) => {
export const rememberAuthAccount = (accountUrl: string) =>
(dispatch: AppDispatch, getState: () => any) => {
dispatch({ type: AUTH_ACCOUNT_REMEMBER_REQUEST, accountUrl });
return KVStore.getItemOrError(`authAccount:${accountUrl}`).then(account => {
dispatch(importFetchedAccount(account));
dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl });
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;
}).catch(error => {
dispatch({ type: AUTH_ACCOUNT_REMEMBER_FAIL, error, accountUrl, skipAlert: true });
});
};
}
export function loadCredentials(token, accountUrl) {
return (dispatch, getState) => {
return dispatch(rememberAuthAccount(accountUrl))
.then(account => account)
.then(() => {
dispatch(verifyCredentials(token, accountUrl));
})
.catch(error => dispatch(verifyCredentials(token, accountUrl)));
};
}
export function logIn(intl, username, password) {
return (dispatch, getState) => {
return dispatch(getAuthApp()).then(() => {
return dispatch(createUserToken(username, password));
}).catch(error => {
if (error.response.data.error === 'mfa_required') {
// If MFA is required, throw the error and handle it in the component.
throw error;
} else if (error.response.data.error === 'invalid_grant') {
// Mastodon returns this user-unfriendly error as a catch-all
// for everything from "bad request" to "wrong password".
// Assume our code is correct and it's a wrong password.
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
} else if (error.response.data.error) {
// If the backend returns an error, display it.
dispatch(snackbar.error(error.response.data.error));
} else {
// Return "wrong password" message.
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
}
export const loadCredentials = (token: string, accountUrl: string) =>
(dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl))
.then(() => {
dispatch(verifyCredentials(token, accountUrl));
})
.catch(() => dispatch(verifyCredentials(token, accountUrl)));
export const logIn = (username: string, password: string) =>
(dispatch: AppDispatch) => dispatch(getAuthApp()).then(() => {
return dispatch(createUserToken(username, password));
}).catch((error: AxiosError) => {
if ((error.response?.data as any).error === 'mfa_required') {
// If MFA is required, throw the error and handle it in the component.
throw error;
});
};
}
} else if ((error.response?.data as any).error === 'invalid_grant') {
// Mastodon returns this user-unfriendly error as a catch-all
// for everything from "bad request" to "wrong password".
// Assume our code is correct and it's a wrong password.
dispatch(snackbar.error(messages.invalidCredentials));
} else if ((error.response?.data as any).error) {
// If the backend returns an error, display it.
dispatch(snackbar.error((error.response?.data as any).error));
} else {
// Return "wrong password" message.
dispatch(snackbar.error(messages.invalidCredentials));
}
throw error;
});
export function deleteSession() {
return (dispatch, getState) => {
return api(getState).delete('/api/sign_out');
};
}
export const deleteSession = () =>
(dispatch: AppDispatch, getState: () => any) => api(getState).delete('/api/sign_out');
export function logOut(intl) {
return (dispatch, getState) => {
export const logOut = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const account = getLoggedInAccount(state);
const standalone = isStandalone(state);
@ -256,9 +241,9 @@ export function logOut(intl) {
if (!account) return dispatch(noOp);
const params = {
client_id: state.getIn(['auth', 'app', 'client_id']),
client_secret: state.getIn(['auth', 'app', 'client_secret']),
token: state.getIn(['auth', 'users', account.url, 'access_token']),
client_id: state.auth.getIn(['app', 'client_id']),
client_secret: state.auth.getIn(['app', 'client_secret']),
token: state.auth.getIn(['users', account.url, 'access_token']),
};
return Promise.all([
@ -266,52 +251,47 @@ export function logOut(intl) {
dispatch(deleteSession()),
]).finally(() => {
dispatch({ type: AUTH_LOGGED_OUT, account, standalone });
dispatch(snackbar.success(intl.formatMessage(messages.loggedOut)));
return dispatch(snackbar.success(messages.loggedOut));
});
};
}
export function switchAccount(accountId, background = false) {
return (dispatch, getState) => {
const account = getState().getIn(['accounts', accountId]);
dispatch({ type: SWITCH_ACCOUNT, account, background });
export const switchAccount = (accountId: string, background = false) =>
(dispatch: AppDispatch, getState: () => any) => {
const account = getState().accounts.get(accountId);
return dispatch({ type: SWITCH_ACCOUNT, account, background });
};
}
export function fetchOwnAccounts() {
return (dispatch, getState) => {
export const fetchOwnAccounts = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
state.getIn(['auth', 'users']).forEach(user => {
const account = state.getIn(['accounts', user.get('id')]);
return state.auth.get('users').forEach((user: ImmutableMap<string, string>) => {
const account = state.accounts.get(user.get('id'));
if (!account) {
dispatch(verifyCredentials(user.get('access_token'), user.get('url')));
dispatch(verifyCredentials(user.get('access_token')!, user.get('url')));
}
});
};
}
export function register(params) {
return (dispatch, getState) => {
export const register = (params: Record<string, any>) =>
(dispatch: AppDispatch) => {
params.fullname = params.username;
return dispatch(createAppAndToken())
.then(() => dispatch(createAccount(params)))
.then(({ token }) => {
.then(({ token }: { token: Record<string, string | number> }) => {
dispatch(startOnboarding());
return dispatch(authLoggedIn(token));
});
};
}
export function fetchCaptcha() {
return (dispatch, getState) => {
export const fetchCaptcha = () =>
(_dispatch: AppDispatch, getState: () => any) => {
return api(getState).get('/api/pleroma/captcha');
};
}
export function authLoggedIn(token) {
return (dispatch, getState) => {
export const authLoggedIn = (token: Record<string, string | number>) =>
(dispatch: AppDispatch) => {
dispatch({ type: AUTH_LOGGED_IN, token });
return token;
};
}

@ -1,5 +1,7 @@
import api from '../api';
import type { AppDispatch } from 'soapbox/store';
export const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST';
export const BACKUPS_FETCH_SUCCESS = 'BACKUPS_FETCH_SUCCESS';
export const BACKUPS_FETCH_FAIL = 'BACKUPS_FETCH_FAIL';
@ -8,24 +10,22 @@ export const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST';
export const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS';
export const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL';
export function fetchBackups() {
return (dispatch, getState) => {
export const fetchBackups = () =>
(dispatch: AppDispatch, getState: () => any) => {
dispatch({ type: BACKUPS_FETCH_REQUEST });
return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) => {
dispatch({ type: BACKUPS_FETCH_SUCCESS, backups });
}).catch(error => {
return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) =>
dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }),
).catch(error => {
dispatch({ type: BACKUPS_FETCH_FAIL, error });
});
};
}
export function createBackup() {
return (dispatch, getState) => {
export const createBackup = () =>
(dispatch: AppDispatch, getState: () => any) => {
dispatch({ type: BACKUPS_CREATE_REQUEST });
return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) => {
dispatch({ type: BACKUPS_CREATE_SUCCESS, backups });
}).catch(error => {
return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) =>
dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }),
).catch(error => {
dispatch({ type: BACKUPS_CREATE_FAIL, error });
});
};
}

@ -18,7 +18,10 @@ import { getQuirks } from 'soapbox/utils/quirks';
import { baseClient } from '../api';
const fetchExternalInstance = baseURL => {
import type { AppDispatch } from 'soapbox/store';
import type { Instance } from 'soapbox/types/entities';
const fetchExternalInstance = (baseURL?: string) => {
return baseClient(null, baseURL)
.get('/api/v1/instance')
.then(({ data: instance }) => normalizeInstance(instance))
@ -33,8 +36,8 @@ const fetchExternalInstance = baseURL => {
});
};
function createExternalApp(instance, baseURL) {
return (dispatch, getState) => {
const createExternalApp = (instance: Instance, baseURL?: string) =>
(dispatch: AppDispatch) => {
// Mitra: skip creating the auth app
if (getQuirks(instance).noApps) return new Promise(f => f({}));
@ -49,14 +52,13 @@ function createExternalApp(instance, baseURL) {
return dispatch(createApp(params, baseURL));
};
}
function externalAuthorize(instance, baseURL) {
return (dispatch, getState) => {
const externalAuthorize = (instance: Instance, baseURL: string) =>
(dispatch: AppDispatch) => {
const { scopes } = getFeatures(instance);
return dispatch(createExternalApp(instance, baseURL)).then(app => {
const { client_id, redirect_uri } = app;
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
const { client_id, redirect_uri } = app as Record<string, string>;
const query = new URLSearchParams({
client_id,
@ -72,58 +74,56 @@ function externalAuthorize(instance, baseURL) {
window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`;
});
};
}
export function externalEthereumLogin(instance, baseURL) {
return (dispatch, getState) => {
const loginMessage = instance.get('login_message');
const externalEthereumLogin = (instance: Instance, baseURL?: string) =>
(dispatch: AppDispatch) => {
const loginMessage = instance.login_message;
return getWalletAndSign(loginMessage).then(({ wallet, signature }) => {
return dispatch(createExternalApp(instance, baseURL)).then(app => {
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
const { client_id, client_secret } = app as Record<string, string>;
const params = {
grant_type: 'ethereum',
wallet_address: wallet.toLowerCase(),
client_id: app.client_id,
client_secret: app.client_secret,
password: signature,
client_id: client_id,
client_secret: client_secret,
password: signature as string,
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
scope: getFeatures(instance).scopes,
};
return dispatch(obtainOAuthToken(params, baseURL))
.then(token => dispatch(authLoggedIn(token)))
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
.then(account => dispatch(switchAccount(account.id)))
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)))
.then(({ access_token }: any) => dispatch(verifyCredentials(access_token, baseURL)))
.then((account: { id: string }) => dispatch(switchAccount(account.id)))
.then(() => window.location.href = '/');
});
});
};
}
export function externalLogin(host) {
return (dispatch, getState) => {
export const externalLogin = (host: string) =>
(dispatch: AppDispatch) => {
const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`);
return fetchExternalInstance(baseURL).then(instance => {
return fetchExternalInstance(baseURL).then((instance) => {
const features = getFeatures(instance);
const quirks = getQuirks(instance);
if (features.ethereumLogin && quirks.noOAuthForm) {
return dispatch(externalEthereumLogin(instance, baseURL));
dispatch(externalEthereumLogin(instance, baseURL));
} else {
return dispatch(externalAuthorize(instance, baseURL));
dispatch(externalAuthorize(instance, baseURL));
}
});
};
}
export function loginWithCode(code) {
return (dispatch, getState) => {
const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app'));
const baseURL = localStorage.getItem('soapbox:external:baseurl');
const scope = localStorage.getItem('soapbox:external:scopes');
export const loginWithCode = (code: string) =>
(dispatch: AppDispatch) => {
const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app')!);
const baseURL = localStorage.getItem('soapbox:external:baseurl')!;
const scope = localStorage.getItem('soapbox:external:scopes')!;
const params = {
const params: Record<string, string> = {
client_id,
client_secret,
redirect_uri,
@ -133,9 +133,8 @@ export function loginWithCode(code) {
};
return dispatch(obtainOAuthToken(params, baseURL))
.then(token => dispatch(authLoggedIn(token)))
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
.then(account => dispatch(switchAccount(account.id)))
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)))
.then(({ access_token }: any) => dispatch(verifyCredentials(access_token as string, baseURL)))
.then((account: { id: string }) => dispatch(switchAccount(account.id)))
.then(() => window.location.href = '/');
};
}

@ -8,6 +8,8 @@
import { baseClient } from '../api';
import type { AppDispatch } from 'soapbox/store';
export const OAUTH_TOKEN_CREATE_REQUEST = 'OAUTH_TOKEN_CREATE_REQUEST';
export const OAUTH_TOKEN_CREATE_SUCCESS = 'OAUTH_TOKEN_CREATE_SUCCESS';
export const OAUTH_TOKEN_CREATE_FAIL = 'OAUTH_TOKEN_CREATE_FAIL';
@ -16,8 +18,8 @@ export const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST';
export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS';
export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL';
export function obtainOAuthToken(params, baseURL) {
return (dispatch, getState) => {
export const obtainOAuthToken = (params: Record<string, string>, baseURL?: string) =>
(dispatch: AppDispatch) => {
dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params });
return baseClient(null, baseURL).post('/oauth/token', params).then(({ data: token }) => {
dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token });
@ -27,10 +29,9 @@ export function obtainOAuthToken(params, baseURL) {
throw error;
});
};
}
export function revokeOAuthToken(params) {
return (dispatch, getState) => {
export const revokeOAuthToken = (params: Record<string, string>) =>
(dispatch: AppDispatch) => {
dispatch({ type: OAUTH_TOKEN_REVOKE_REQUEST, params });
return baseClient().post('/oauth/revoke', params).then(({ data }) => {
dispatch({ type: OAUTH_TOKEN_REVOKE_SUCCESS, params, data });
@ -40,4 +41,3 @@ export function revokeOAuthToken(params) {
throw error;
});
};
}

@ -53,7 +53,7 @@ const getAuthBaseURL = createSelector([
* @param {string} baseURL
* @returns {object} Axios instance
*/
export const baseClient = (accessToken: string, baseURL: string = ''): AxiosInstance => {
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,

@ -25,7 +25,7 @@ const BirthdayInput: React.FC<IBirthdayInput> = ({ value, onChange, required })
const features = useFeatures();
const supportsBirthdays = features.birthdays;
const minAge = useAppSelector((state) => state.instance.getIn(['pleroma', 'metadata', 'birthday_min_age'])) as number;
const minAge = useAppSelector((state) => state.instance.pleroma.getIn(['metadata', 'birthday_min_age'])) as number;
const maxDate = useMemo(() => {
if (!supportsBirthdays) return null;

@ -18,7 +18,7 @@ const SidebarNavigation = () => {
const settings = useAppSelector((state) => getSettings(state));
const account = useOwnAccount();
const notificationCount = useAppSelector((state) => state.notifications.get('unread'));
const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: any, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0));
const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0));
const followRequestsCount = useAppSelector((state) => state.user_lists.getIn(['follow_requests', 'items'], ImmutableOrderedSet()).count());
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());

@ -109,7 +109,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const onClickLogOut: React.MouseEventHandler = (e) => {
e.preventDefault();
dispatch(logOut(intl));
dispatch(logOut());
};
const handleSwitcherClick: React.MouseEventHandler = (e) => {

@ -8,7 +8,7 @@ import { getFeatures } from 'soapbox/utils/features';
const ThumbNavigation: React.FC = (): JSX.Element => {
const account = useOwnAccount();
const notificationCount = useAppSelector((state) => state.notifications.unread);
const chatsCount = useAppSelector((state) => state.chats.get('items').reduce((acc: number, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0));
const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0));
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
const features = getFeatures(useAppSelector((state) => state.instance));

@ -6,6 +6,8 @@ import { fetchCaptcha } from 'soapbox/actions/auth';
import { Stack, Text, Input } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import type { AxiosResponse } from 'axios';
const noOp = () => {};
const messages = defineMessages({
@ -39,7 +41,7 @@ const CaptchaField: React.FC<ICaptchaField> = ({
const [refresh, setRefresh] = useState<NodeJS.Timer | undefined>(undefined);
const getCaptcha = () => {
dispatch(fetchCaptcha()).then(response => {
dispatch(fetchCaptcha()).then((response: AxiosResponse) => {
const captcha = ImmutableMap<string, any>(response.data);
setCaptcha(captcha);
onFetch(captcha);

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { useIntl } from 'react-intl';
import { Redirect } from 'react-router-dom';
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
@ -15,7 +14,6 @@ import OtpAuthForm from './otp_auth_form';
import type { AxiosError } from 'axios';
const LoginPage = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const me = useAppSelector((state) => state.me);
@ -36,8 +34,8 @@ const LoginPage = () => {
const handleSubmit: React.FormEventHandler = (event) => {
const { username, password } = getFormData(event.target as HTMLFormElement);
dispatch(logIn(intl, username, password)).then(({ access_token }: { access_token: string }) => {
return dispatch(verifyCredentials(access_token))
dispatch(logIn(username, password)).then(({ access_token }) => {
return dispatch(verifyCredentials(access_token as string))
// Refetch the instance for authenticated fetch
.then(() => dispatch(fetchInstance() as any));
}).then((account: { id: string }) => {

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Redirect } from 'react-router-dom';
@ -8,12 +7,11 @@ import { Spinner } from 'soapbox/components/ui';
/** Component that logs the user out when rendered */
const Logout: React.FC = () => {
const intl = useIntl();
const dispatch = useDispatch();
const [done, setDone] = useState(false);
useEffect(() => {
dispatch(logOut(intl) as any)
dispatch(logOut() as any)
.then(() => setDone(true))
.catch(console.warn);
}, []);

@ -32,8 +32,8 @@ const OtpAuthForm: React.FC<IOtpAuthForm> = ({ mfa_token }) => {
const { code } = getFormData(event.target);
dispatch(otpVerify(code, mfa_token)).then(({ access_token }) => {
setCodeError(false);
return dispatch(verifyCredentials(access_token));
}).then(account => {
return dispatch(verifyCredentials(access_token as string));
}).then((account: Record<string, any>) => {
setShouldRedirect(true);
return dispatch(switchAccount(account.id));
}).catch(() => {

@ -2,10 +2,7 @@ import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import {
fetchBackups,
createBackup,
} from 'soapbox/actions/backups';
import { fetchBackups, createBackup } from 'soapbox/actions/backups';
import ScrollableList from 'soapbox/components/scrollable_list';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';

@ -20,7 +20,7 @@ interface IChat {
const Chat: React.FC<IChat> = ({ chatId, onClick }) => {
const chat = useAppSelector((state) => {
const chat = state.chats.getIn(['items', chatId]);
const chat = state.chats.items.get(chatId);
return chat ? getChat(state, (chat as any).toJS()) : undefined;
}) as ChatEntity;

@ -49,9 +49,9 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false })
const dispatch = useDispatch();
const intl = useIntl();
const chatIds = useAppSelector(state => sortedChatIdsSelector(state.chats.get('items')));
const hasMore = useAppSelector(state => !!state.chats.get('next'));
const isLoading = useAppSelector(state => state.chats.get('isLoading'));
const chatIds = useAppSelector(state => sortedChatIdsSelector(state.chats.items));
const hasMore = useAppSelector(state => !!state.chats.next);
const isLoading = useAppSelector(state => state.chats.isLoading);
const handleLoadMore = useCallback(() => {
if (hasMore && !isLoading) {

@ -1,127 +0,0 @@
import { List as ImmutableList } from 'immutable';
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
import { openChat, launchChat, toggleMainWindow } from 'soapbox/actions/chats';
import { getSettings } from 'soapbox/actions/settings';
import AccountSearch from 'soapbox/components/account_search';
import { Counter } from 'soapbox/components/ui';
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
import ChatList from './chat_list';
import ChatWindow from './chat_window';
const messages = defineMessages({
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
});
const getChatsUnreadCount = state => {
const chats = state.getIn(['chats', 'items']);
return chats.reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0);
};
// Filter out invalid chats
const normalizePanes = (chats, panes = ImmutableList()) => (
panes.filter(pane => chats.get(pane.get('chat_id')))
);
const makeNormalizeChatPanes = () => createSelector([
state => state.getIn(['chats', 'items']),
state => getSettings(state).getIn(['chats', 'panes']),
], normalizePanes);
const makeMapStateToProps = () => {
const mapStateToProps = state => {
const normalizeChatPanes = makeNormalizeChatPanes();
return {
panes: normalizeChatPanes(state),
mainWindowState: getSettings(state).getIn(['chats', 'mainWindow']),
unreadCount: getChatsUnreadCount(state),
};
};
return mapStateToProps;
};
export default @connect(makeMapStateToProps)
@injectIntl
@withRouter
class ChatPanes extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
mainWindowState: PropTypes.string,
panes: ImmutablePropTypes.list,
history: PropTypes.object,
}
handleClickChat = (chat) => {
this.props.dispatch(openChat(chat.get('id')));
}
handleSuggestion = accountId => {
this.props.dispatch(launchChat(accountId, this.props.history));
}
handleMainWindowToggle = () => {
this.props.dispatch(toggleMainWindow());
}
render() {
const { intl, panes, mainWindowState, unreadCount } = this.props;
const open = mainWindowState === 'open';
const mainWindowPane = (
<div className={`pane pane--main pane--${mainWindowState}`}>
<div className='pane__header'>
{unreadCount > 0 && (
<div className='mr-2 flex-none'>
<Counter count={unreadCount} />
</div>
)}
<button className='pane__title' onClick={this.handleMainWindowToggle}>
<FormattedMessage id='chat_panels.main_window.title' defaultMessage='Chats' />
</button>
<AudioToggle />
</div>
<div className='pane__content'>
{open && (
<>
<ChatList
onClickChat={this.handleClickChat}
/>
<AccountSearch
placeholder={intl.formatMessage(messages.searchPlaceholder)}
onSelected={this.handleSuggestion}
resultsPosition='above'
/>
</>
)}
</div>
</div>
);
return (
<div className='chat-panes'>
{mainWindowPane}
{panes.map((pane, i) => (
<ChatWindow
idx={i}
key={pane.get('chat_id')}
chatId={pane.get('chat_id')}
windowState={pane.get('state')}
/>
))}
</div>
);
}
}

@ -0,0 +1,108 @@
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { createSelector } from 'reselect';
import { openChat, launchChat, toggleMainWindow } from 'soapbox/actions/chats';
import { getSettings } from 'soapbox/actions/settings';
import AccountSearch from 'soapbox/components/account_search';
import { Counter } from 'soapbox/components/ui';
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
import { RootState } from 'soapbox/store';
import { Chat } from 'soapbox/types/entities';
import ChatList from './chat_list';
import ChatWindow from './chat_window';
const messages = defineMessages({
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
});
const getChatsUnreadCount = (state: RootState) => {
const chats = state.chats.items;
return chats.reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0);
};
// Filter out invalid chats
const normalizePanes = (chats: Immutable.Map<string, Chat>, panes = ImmutableList<ImmutableMap<string, any>>()) => (
panes.filter(pane => chats.get(pane.get('chat_id')))
);
const makeNormalizeChatPanes = () => createSelector([
(state: RootState) => state.chats.items,
(state: RootState) => getSettings(state).getIn(['chats', 'panes']) as any,
], normalizePanes);
const normalizeChatPanes = makeNormalizeChatPanes();
const ChatPanes = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const panes = useAppSelector((state) => normalizeChatPanes(state));
const mainWindowState = useSettings().getIn(['chats', 'mainWindow']);
const unreadCount = useAppSelector((state) => getChatsUnreadCount(state));
const handleClickChat = ((chat: Chat) => {
dispatch(openChat(chat.id));
});
const handleSuggestion = (accountId: string) => {
dispatch(launchChat(accountId, history));
};
const handleMainWindowToggle = () => {
dispatch(toggleMainWindow());
};
const open = mainWindowState === 'open';
const mainWindowPane = (
<div className={`pane pane--main pane--${mainWindowState}`}>
<div className='pane__header'>
{unreadCount > 0 && (
<div className='mr-2 flex-none'>
<Counter count={unreadCount} />
</div>
)}
<button className='pane__title' onClick={handleMainWindowToggle}>
<FormattedMessage id='chat_panels.main_window.title' defaultMessage='Chats' />
</button>
<AudioToggle />
</div>
<div className='pane__content'>
{open && (
<>
<ChatList
onClickChat={handleClickChat}
/>
<AccountSearch
placeholder={intl.formatMessage(messages.searchPlaceholder)}
onSelected={handleSuggestion}
resultsPosition='above'
/>
</>
)}
</div>
</div>
);
return (
<div className='chat-panes'>
{mainWindowPane}
{panes.map((pane, i) => (
<ChatWindow
idx={i}
key={pane.get('chat_id')}
chatId={pane.get('chat_id')}
windowState={pane.get('state')}
/>
))}
</div>
);
};
export default ChatPanes;

@ -1,12 +0,0 @@
// @preval
// Converts cryptocurrency-icon's manifest file from a list to a map.
// See: https://github.com/spothq/cryptocurrency-icons/blob/master/manifest.json
const manifest = require('cryptocurrency-icons/manifest.json');
const { Map: ImmutableMap, fromJS } = require('immutable');
const manifestMap = fromJS(manifest).reduce((acc, entry) => {
return acc.set(entry.get('symbol').toLowerCase(), entry);
}, ImmutableMap());
module.exports = manifestMap.toJS();

@ -0,0 +1,11 @@
// Converts cryptocurrency-icon's manifest file from a list to a map.
// See: https://github.com/spothq/cryptocurrency-icons/blob/master/manifest.json
import manifest from 'cryptocurrency-icons/manifest.json';
import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable';
const manifestMap = (fromJS(manifest) as ImmutableList<ImmutableMap<string, string>>).reduce((acc: ImmutableMap<string, ImmutableMap<string, string>>, entry: ImmutableMap<string, string>) => {
return acc.set(entry.get('symbol')!.toLowerCase(), entry);
}, ImmutableMap());
export default manifestMap.toJS();

@ -48,7 +48,7 @@ const Header = () => {
event.preventDefault();
setLoading(true);
dispatch(logIn(intl, username, password) as any)
dispatch(logIn(username, password) as any)
.then(({ access_token }: { access_token: string }) => {
return (
dispatch(verifyCredentials(access_token) as any)

@ -1,6 +1,6 @@
import classNames from 'classnames';
import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
@ -27,11 +27,10 @@ const LinkFooter: React.FC = (): JSX.Element => {
const features = useFeatures();
const soapboxConfig = useSoapboxConfig();
const intl = useIntl();
const dispatch = useDispatch();
const onClickLogOut: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(logOut(intl));
dispatch(logOut());
e.preventDefault();
};

@ -44,7 +44,7 @@ const Navbar = () => {
event.preventDefault();
setLoading(true);
dispatch(logIn(intl, username, password) as any)
dispatch(logIn(username, password) as any)
.then(({ access_token }: { access_token: string }) => {
setLoading(false);

@ -43,7 +43,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.get('id'))));
const handleLogOut = () => {
dispatch(logOut(intl));
dispatch(logOut());
};
const handleSwitchAccount = (account: AccountEntity) => {

@ -52,7 +52,7 @@ const Registration = () => {
event.preventDefault();
dispatch(createAccount(username, password))
.then(() => dispatch(logIn(intl, username, password)))
.then(() => dispatch(logIn(username, password)))
.then(({ access_token }: any) => dispatch(verifyCredentials(access_token)))
.then(() => dispatch(fetchInstance()))
.then(() => {

@ -1,5 +1,4 @@
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
@ -12,7 +11,6 @@ import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
const WaitlistPage = (/* { account } */) => {
const dispatch = useDispatch();
const intl = useIntl();
const title = useAppSelector((state) => state.instance.title);
const me = useOwnAccount();
@ -20,7 +18,7 @@ const WaitlistPage = (/* { account } */) => {
const onClickLogOut: React.MouseEventHandler = (event) => {
event.preventDefault();
dispatch(logOut(intl));
dispatch(logOut());
};
const openVerifySmsModal = () => dispatch(openModal('VERIFY_SMS'));

@ -1,5 +1,5 @@
import { useDispatch } from 'react-redux';
import { AppDispatch } from 'soapbox/store';
import type { AppDispatch } from 'soapbox/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();

@ -27,6 +27,7 @@ describe('normalizeInstance()', () => {
fedibird_capabilities: [],
invites_enabled: false,
languages: [],
login_message: '',
pleroma: {
metadata: {
account_activation_required: false,

@ -39,6 +39,7 @@ export const InstanceRecord = ImmutableRecord({
fedibird_capabilities: ImmutableList(),
invites_enabled: false,
languages: ImmutableList(),
login_message: '',
pleroma: ImmutableMap<string, any>({
metadata: ImmutableMap<string, any>({
account_activation_required: false,

@ -244,8 +244,8 @@ type APIChat = { id: string, last_message: string };
export const makeGetChat = () => {
return createSelector(
[
(state: RootState, { id }: APIChat) => state.chats.getIn(['items', id]) as ReducerChat,
(state: RootState, { id }: APIChat) => state.accounts.get(state.chats.getIn(['items', id, 'account'])),
(state: RootState, { id }: APIChat) => state.chats.items.get(id) as ReducerChat,
(state: RootState, { id }: APIChat) => state.accounts.get(state.chats.items.getIn([id, 'account'])),
(state: RootState, { last_message }: APIChat) => state.chat_messages.get(last_message),
],

@ -30,11 +30,11 @@ export const isLoggedIn = (getState: () => RootState) => {
return validId(getState().me);
};
export const getAppToken = (state: RootState) => state.auth.getIn(['app', 'access_token']);
export const getAppToken = (state: RootState) => state.auth.getIn(['app', 'access_token']) as string;
export const getUserToken = (state: RootState, accountId?: string | false | null) => {
const accountUrl = state.accounts.getIn([accountId, 'url']);
return state.auth.getIn(['users', accountUrl, 'access_token']);
return state.auth.getIn(['users', accountUrl, 'access_token']) as string;
};
export const getAccessToken = (state: RootState) => {

@ -1,26 +0,0 @@
export const ethereum = () => window.ethereum;
export const hasEthereum = () => Boolean(ethereum());
// Requests an Ethereum wallet from the browser
// Returns a Promise containing the Ethereum wallet address (string).
export const getWallet = () => {
return ethereum().request({ method: 'eth_requestAccounts' })
.then(wallets => wallets[0]);
};
// Asks the browser to sign a message with Ethereum.
// Returns a Promise containing the signature (string).
export const signMessage = (wallet, message) => {
return ethereum().request({ method: 'personal_sign', params: [message, wallet] });
};
// Combines the above functions.
// Returns an object with the `wallet` and `signature`
export const getWalletAndSign = message => {
return getWallet().then(wallet => {
return signMessage(wallet, message).then(signature => {
return { wallet, signature };
});
});
};

@ -0,0 +1,28 @@
import type { MetaMaskInpageProvider } from '@metamask/providers';
export const ethereum: () => MetaMaskInpageProvider | undefined = () => (window as any).ethereum;
export const hasEthereum = () => Boolean(ethereum());
// Requests an Ethereum wallet from the browser
// Returns a Promise containing the Ethereum wallet address (string).
export const getWallet: () => Promise<string> = () => {
return ethereum()!.request({ method: 'eth_requestAccounts' })
.then((wallets) => (wallets as Array<string>)[0]);
};
// Asks the browser to sign a message with Ethereum.
// Returns a Promise containing the signature (string).
export const signMessage = (wallet: string, message: string) => {
return ethereum()!.request({ method: 'personal_sign', params: [message, wallet] });
};
// Combines the above functions.
// Returns an object with the `wallet` and `signature`
export const getWalletAndSign = (message: string) => {
return getWallet().then(wallet => {
return signMessage(wallet, message).then(signature => {
return { wallet, signature };
});
});
};

@ -55,6 +55,7 @@
"@gamestdio/websocket": "^0.3.2",
"@jest/globals": "^27.5.1",
"@lcdp/offline-plugin": "^5.1.0",
"@metamask/providers": "^9.0.0",
"@popperjs/core": "^2.4.4",
"@reach/menu-button": "^0.16.2",
"@reach/popover": "^0.16.2",

@ -1848,6 +1848,38 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
"@metamask/object-multiplex@^1.1.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.2.0.tgz#38fc15c142f61939391e1b9a8eed679696c7e4f4"
integrity sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==
dependencies:
end-of-stream "^1.4.4"
once "^1.4.0"
readable-stream "^2.3.3"
"@metamask/providers@^9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-9.0.0.tgz#644684f9eceb952138e80afb9103c7e39d8350fe"
integrity sha512-9qUkaFafZUROK0CAUBqjsut+7mqKOXFhBCpAhAPVRBqj5TfUTdPI4t8S7GYzPVaDbC7M6kH/YLNCgcfaFWAS+w==
dependencies:
"@metamask/object-multiplex" "^1.1.0"
"@metamask/safe-event-emitter" "^2.0.0"
"@types/chrome" "^0.0.136"
detect-browser "^5.2.0"
eth-rpc-errors "^4.0.2"
extension-port-stream "^2.0.1"
fast-deep-equal "^2.0.1"
is-stream "^2.0.0"
json-rpc-engine "^6.1.0"
json-rpc-middleware-stream "^3.0.0"
pump "^3.0.0"
webextension-polyfill-ts "^0.25.0"
"@metamask/safe-event-emitter@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c"
integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -2252,6 +2284,14 @@
dependencies:
"@types/node" "*"
"@types/chrome@^0.0.136":
version "0.0.136"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.136.tgz#7c011b9f997b0156f25a140188a0c5689d3f368f"
integrity sha512-XDEiRhLkMd+SB7Iw3ZUIj/fov3wLd4HyTdLltVszkgl1dBfc3Rb7oPMVZ2Mz2TLqnF7Ow+StbR8E7r9lqpb4DA==
dependencies:
"@types/filesystem" "*"
"@types/har-format" "*"
"@types/connect-history-api-fallback@^1.3.5":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae"
@ -2317,6 +2357,18 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/filesystem@*":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.32.tgz#307df7cc084a2293c3c1a31151b178063e0a8edf"
integrity sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==
dependencies:
"@types/filewriter" "*"
"@types/filewriter@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee"
integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==
"@types/fs-extra@^9.0.1":
version "9.0.13"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
@ -2331,6 +2383,11 @@
dependencies:
"@types/node" "*"
"@types/har-format@*":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.8.tgz#e6908b76d4c88be3db642846bb8b455f0bfb1c4e"
integrity sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
@ -4480,6 +4537,11 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
detect-browser@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca"
integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==
detect-it@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/detect-it/-/detect-it-4.0.1.tgz#3f8de6b8330f5086270571251bedf10aec049e18"
@ -4742,6 +4804,13 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
end-of-stream@^1.1.0, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
enhanced-resolve@^5.0.0:
version "5.9.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee"
@ -5149,6 +5218,13 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
eth-rpc-errors@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a"
integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==
dependencies:
fast-safe-stringify "^2.0.6"
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@ -5255,6 +5331,13 @@ extend@^3.0.0:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extension-port-stream@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-2.0.1.tgz#d374820c581418c2275d3c4439ade0b82c4cfac6"
integrity sha512-ltrv4Dh/979I04+D4Te6TFygfRSOc5EBzzlHRldWMS8v73V80qWluxH88hqF0qyUsBXTb8NmzlmSipcre6a+rg==
dependencies:
webextension-polyfill-ts "^0.22.0"
fake-indexeddb@^3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-3.1.7.tgz#d9efbeade113c15efbe862e4598a4b0a1797ed9f"
@ -5262,6 +5345,11 @@ fake-indexeddb@^3.1.7:
dependencies:
realistic-structured-clone "^2.0.1"
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -5299,6 +5387,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-safe-stringify@^2.0.6:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
fastest-levenshtein@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
@ -7052,6 +7145,22 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-rpc-engine@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz#bf5ff7d029e1c1bf20cb6c0e9f348dcd8be5a393"
integrity sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==
dependencies:
"@metamask/safe-event-emitter" "^2.0.0"
eth-rpc-errors "^4.0.2"
json-rpc-middleware-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-3.0.0.tgz#8540331d884f36b9e0ad31054cc68ac6b5a89b52"
integrity sha512-JmZmlehE0xF3swwORpLHny/GvW3MZxCsb2uFNBrn8TOqMqivzCfz232NSDLLOtIQlrPlgyEjiYpyzyOPFOzClw==
dependencies:
"@metamask/safe-event-emitter" "^2.0.0"
readable-stream "^2.3.3"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -8000,7 +8109,7 @@ on-headers@~1.0.2:
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
once@^1.3.0:
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@ -8738,6 +8847,14 @@ psl@^1.1.33:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -9166,7 +9283,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
readable-stream@^2.0.1:
readable-stream@^2.0.1, readable-stream@^2.3.3:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@ -10876,6 +10993,25 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies:
minimalistic-assert "^1.0.0"
webextension-polyfill-ts@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/webextension-polyfill-ts/-/webextension-polyfill-ts-0.22.0.tgz#86cfd7bab4d9d779d98c8340983f4b691b2343f3"
integrity sha512-3P33ClMwZ/qiAT7UH1ROrkRC1KM78umlnPpRhdC/292UyoTTW9NcjJEqDsv83HbibcTB6qCtpVeuB2q2/oniHQ==
dependencies:
webextension-polyfill "^0.7.0"
webextension-polyfill-ts@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/webextension-polyfill-ts/-/webextension-polyfill-ts-0.25.0.tgz#fff041626365dbd0e29c40b197e989a55ec221ca"
integrity sha512-ikQhwwHYkpBu00pFaUzIKY26I6L87DeRI+Q6jBT1daZUNuu8dSrg5U9l/ZbqdaQ1M/TTSPKeAa3kolP5liuedw==
dependencies:
webextension-polyfill "^0.7.0"
webextension-polyfill@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.7.0.tgz#0df1120ff0266056319ce1a622b09ad8d4a56505"
integrity sha512-su48BkMLxqzTTvPSE1eWxKToPS2Tv5DLGxKexLEVpwFd6Po6N8hhSLIvG6acPAg7qERoEaDL+Y5HQJeJeml5Aw==
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"

Loading…
Cancel
Save