kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Update immutable-js, fix tests, fix reducers/auth
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>environments/review-cleanup-4am6ej/deployments/1865
rodzic
d1071a109b
commit
a7653403e7
|
@ -96,8 +96,8 @@ const createAppToken = () =>
|
||||||
const app = getState().auth.app;
|
const app = getState().auth.app;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_id: app.client_id,
|
client_id: app.client_id!,
|
||||||
client_secret: app.client_secret,
|
client_secret: app.client_secret!,
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
grant_type: 'client_credentials',
|
grant_type: 'client_credentials',
|
||||||
scope: getScopes(getState()),
|
scope: getScopes(getState()),
|
||||||
|
@ -113,8 +113,8 @@ const createUserToken = (username: string, password: string) =>
|
||||||
const app = getState().auth.app;
|
const app = getState().auth.app;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_id: app.client_id,
|
client_id: app.client_id!,
|
||||||
client_secret: app.client_secret,
|
client_secret: app.client_secret!,
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
grant_type: 'password',
|
grant_type: 'password',
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -190,7 +190,7 @@ export const logIn = (username: string, password: string) =>
|
||||||
(dispatch: AppDispatch) => dispatch(getAuthApp()).then(() => {
|
(dispatch: AppDispatch) => dispatch(getAuthApp()).then(() => {
|
||||||
return dispatch(createUserToken(normalizeUsername(username), password));
|
return dispatch(createUserToken(normalizeUsername(username), password));
|
||||||
}).catch((error: AxiosError) => {
|
}).catch((error: AxiosError) => {
|
||||||
if ((error.response?.data as any).error === 'mfa_required') {
|
if ((error.response?.data as any)?.error === 'mfa_required') {
|
||||||
// If MFA is required, throw the error and handle it in the component.
|
// If MFA is required, throw the error and handle it in the component.
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,8 +212,8 @@ export const logOut = () =>
|
||||||
if (!account) return dispatch(noOp);
|
if (!account) return dispatch(noOp);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_id: state.auth.app.client_id,
|
client_id: state.auth.app.client_id!,
|
||||||
client_secret: state.auth.app.client_secret,
|
client_secret: state.auth.app.client_secret!,
|
||||||
token: state.auth.users.get(account.url)?.access_token!,
|
token: state.auth.users.get(account.url)?.access_token!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ export const fetchOwnAccounts = () =>
|
||||||
return state.auth.users.forEach((user) => {
|
return state.auth.users.forEach((user) => {
|
||||||
const account = state.accounts.get(user.id);
|
const account = state.accounts.get(user.id);
|
||||||
if (!account) {
|
if (!account) {
|
||||||
dispatch(verifyCredentials(user.access_token!, user.url));
|
dispatch(verifyCredentials(user.access_token!, user.url!));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,6 @@ import StatusReplyMentions from './status-reply-mentions';
|
||||||
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
||||||
import { Card, HStack, Stack, Text } from './ui';
|
import { Card, HStack, Stack, Text } from './ui';
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
|
||||||
import type {
|
import type {
|
||||||
Account as AccountEntity,
|
Account as AccountEntity,
|
||||||
Status as StatusEntity,
|
Status as StatusEntity,
|
||||||
|
|
|
@ -7,8 +7,6 @@ import { Button, Card, CardBody, CardHeader, CardTitle, Column, Spinner, Stack,
|
||||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
import { Token } from 'soapbox/reducers/security';
|
import { Token } from 'soapbox/reducers/security';
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
header: { id: 'security.headers.tokens', defaultMessage: 'Sessions' },
|
header: { id: 'security.headers.tokens', defaultMessage: 'Sessions' },
|
||||||
revoke: { id: 'security.tokens.revoke', defaultMessage: 'Revoke' },
|
revoke: { id: 'security.tokens.revoke', defaultMessage: 'Revoke' },
|
||||||
|
|
|
@ -21,12 +21,13 @@ const shouldShowError = ({ type, skipAlert, error }: AnyAction): boolean => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Middleware to display Redux errors to the user. */
|
/** Middleware to display Redux errors to the user. */
|
||||||
export default function errorsMiddleware(): ThunkMiddleware {
|
const errorsMiddleware = (): ThunkMiddleware =>
|
||||||
return () => next => action => {
|
() => next => action => {
|
||||||
if (shouldShowError(action)) {
|
if (shouldShowError(action)) {
|
||||||
toast.showAlertForError(action.error);
|
toast.showAlertForError(action.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
export default errorsMiddleware;
|
||||||
|
|
|
@ -16,9 +16,9 @@ const AdminPage: React.FC = ({ children }) => {
|
||||||
</Layout.Main>
|
</Layout.Main>
|
||||||
|
|
||||||
<Layout.Aside>
|
<Layout.Aside>
|
||||||
{/* <BundleContainer fetchComponent={LatestAccountsPanel}>
|
<BundleContainer fetchComponent={LatestAccountsPanel}>
|
||||||
{Component => <Component limit={5} />}
|
{Component => <Component limit={5} />}
|
||||||
</BundleContainer> */}
|
</BundleContainer>
|
||||||
|
|
||||||
<LinkFooter />
|
<LinkFooter />
|
||||||
</Layout.Aside>
|
</Layout.Aside>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AUTH_APP_CREATED,
|
AUTH_APP_CREATED,
|
||||||
|
@ -10,18 +10,18 @@ import {
|
||||||
} from 'soapbox/actions/auth';
|
} from 'soapbox/actions/auth';
|
||||||
import { ME_FETCH_SKIP } from 'soapbox/actions/me';
|
import { ME_FETCH_SKIP } from 'soapbox/actions/me';
|
||||||
import { MASTODON_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
import { MASTODON_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||||
import { ReducerRecord } from 'soapbox/reducers/auth';
|
import { AuthAppRecord, AuthTokenRecord, AuthUserRecord, ReducerRecord } from 'soapbox/reducers/auth';
|
||||||
|
|
||||||
import reducer from '../auth';
|
import reducer from '../auth';
|
||||||
|
|
||||||
describe('auth reducer', () => {
|
describe('auth reducer', () => {
|
||||||
it('should return the initial state', () => {
|
it('should return the initial state', () => {
|
||||||
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap({
|
expect(reducer(undefined, {} as any).toJS()).toMatchObject({
|
||||||
app: ImmutableMap(),
|
app: {},
|
||||||
users: ImmutableMap(),
|
users: {},
|
||||||
tokens: ImmutableMap(),
|
tokens: {},
|
||||||
me: null,
|
me: null,
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AUTH_APP_CREATED', () => {
|
describe('AUTH_APP_CREATED', () => {
|
||||||
|
@ -30,9 +30,9 @@ describe('auth reducer', () => {
|
||||||
const action = { type: AUTH_APP_CREATED, app: token };
|
const action = { type: AUTH_APP_CREATED, app: token };
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
const expected = fromJS(token);
|
const expected = AuthAppRecord(token);
|
||||||
|
|
||||||
expect(result.get('app')).toEqual(expected);
|
expect(result.app).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,19 +42,19 @@ describe('auth reducer', () => {
|
||||||
const action = { type: AUTH_LOGGED_IN, token };
|
const action = { type: AUTH_LOGGED_IN, token };
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
const expected = fromJS({ 'ABCDEFG': token });
|
const expected = ImmutableMap({ 'ABCDEFG': AuthTokenRecord(token) });
|
||||||
|
|
||||||
expect(result.get('tokens')).toEqual(expected);
|
expect(result.tokens).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should merge the token with existing state', () => {
|
it('should merge the token with existing state', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
tokens: { 'ABCDEFG': { token_type: 'Bearer', access_token: 'ABCDEFG' } },
|
tokens: ImmutableMap({ 'ABCDEFG': AuthTokenRecord({ token_type: 'Bearer', access_token: 'ABCDEFG' }) }),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'ABCDEFG': { token_type: 'Bearer', access_token: 'ABCDEFG' },
|
'ABCDEFG': AuthTokenRecord({ token_type: 'Bearer', access_token: 'ABCDEFG' }),
|
||||||
'HIJKLMN': { token_type: 'Bearer', access_token: 'HIJKLMN' },
|
'HIJKLMN': AuthTokenRecord({ token_type: 'Bearer', access_token: 'HIJKLMN' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
|
@ -63,7 +63,7 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('tokens')).toEqual(expected);
|
expect(result.tokens).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -74,29 +74,29 @@ describe('auth reducer', () => {
|
||||||
account: fromJS({ url: 'https://gleasonator.com/users/alex' }),
|
account: fromJS({ url: 'https://gleasonator.com/users/alex' }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('users')).toEqual(expected);
|
expect(result.users).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets `me` to the next available user', () => {
|
it('sets `me` to the next available user', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
me: 'https://gleasonator.com/users/alex',
|
me: 'https://gleasonator.com/users/alex',
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: AUTH_LOGGED_OUT,
|
type: AUTH_LOGGED_OUT,
|
||||||
|
@ -104,7 +104,7 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('me')).toEqual('https://gleasonator.com/users/benis');
|
expect(result.me).toEqual('https://gleasonator.com/users/benis');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,12 +116,12 @@ describe('auth reducer', () => {
|
||||||
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
expect(result.get('users')).toEqual(expected);
|
expect(result.users).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the account in the token', () => {
|
it('should set the account in the token', () => {
|
||||||
|
@ -131,21 +131,21 @@ describe('auth reducer', () => {
|
||||||
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
tokens: { 'ABCDEFG': { token_type: 'Bearer', access_token: 'ABCDEFG' } },
|
tokens: ImmutableMap({ 'ABCDEFG': AuthTokenRecord({ token_type: 'Bearer', access_token: 'ABCDEFG' }) }),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = {
|
||||||
'ABCDEFG': {
|
'ABCDEFG': {
|
||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
access_token: 'ABCDEFG',
|
access_token: 'ABCDEFG',
|
||||||
account: '1234',
|
account: '1234',
|
||||||
me: 'https://gleasonator.com/users/alex',
|
me: 'https://gleasonator.com/users/alex',
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('tokens')).toEqual(expected);
|
expect(result.tokens.toJS()).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets `me` to the account if unset', () => {
|
it('sets `me` to the account if unset', () => {
|
||||||
|
@ -156,7 +156,7 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
expect(result.get('me')).toEqual('https://gleasonator.com/users/alex');
|
expect(result.me).toEqual('https://gleasonator.com/users/alex');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves `me` alone if already set', () => {
|
it('leaves `me` alone if already set', () => {
|
||||||
|
@ -166,10 +166,10 @@ describe('auth reducer', () => {
|
||||||
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({ me: 'https://gleasonator.com/users/benis' })));
|
const state = ReducerRecord({ me: 'https://gleasonator.com/users/benis' });
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('me')).toEqual('https://gleasonator.com/users/benis');
|
expect(result.me).toEqual('https://gleasonator.com/users/benis');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes mismatched users', () => {
|
it('deletes mismatched users', () => {
|
||||||
|
@ -179,21 +179,21 @@ describe('auth reducer', () => {
|
||||||
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'https://gleasonator.com/users/mk': { id: '4567', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/mk' },
|
'https://gleasonator.com/users/mk': AuthUserRecord({ id: '4567', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/mk' }),
|
||||||
'https://gleasonator.com/users/curtis': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/curtis' },
|
'https://gleasonator.com/users/curtis': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/curtis' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5432', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5432', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5432', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5432', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('users')).toEqual(expected);
|
expect(result.users).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('upgrades from an ID to a URL', () => {
|
it('upgrades from an ID to a URL', () => {
|
||||||
|
@ -203,18 +203,18 @@ describe('auth reducer', () => {
|
||||||
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
account: { id: '1234', url: 'https://gleasonator.com/users/alex' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
me: '1234',
|
me: '1234',
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'1234': { id: '1234', access_token: 'ABCDEFG' },
|
'1234': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG' }),
|
||||||
'5432': { id: '5432', access_token: 'HIJKLMN' },
|
'5432': AuthUserRecord({ id: '5432', access_token: 'HIJKLMN' }),
|
||||||
},
|
}),
|
||||||
tokens: {
|
tokens: ImmutableMap({
|
||||||
'ABCDEFG': { access_token: 'ABCDEFG', account: '1234' },
|
'ABCDEFG': AuthTokenRecord({ access_token: 'ABCDEFG', account: '1234' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = ImmutableRecord(fromJS({
|
const expected = {
|
||||||
me: 'https://gleasonator.com/users/alex',
|
me: 'https://gleasonator.com/users/alex',
|
||||||
users: {
|
users: {
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
||||||
|
@ -223,24 +223,24 @@ describe('auth reducer', () => {
|
||||||
tokens: {
|
tokens: {
|
||||||
'ABCDEFG': { access_token: 'ABCDEFG', account: '1234', me: 'https://gleasonator.com/users/alex' },
|
'ABCDEFG': { access_token: 'ABCDEFG', account: '1234', me: 'https://gleasonator.com/users/alex' },
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result).toEqual(expected);
|
expect(result.toJS()).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('VERIFY_CREDENTIALS_FAIL', () => {
|
describe('VERIFY_CREDENTIALS_FAIL', () => {
|
||||||
it('should delete the failed token if it 403\'d', () => {
|
it('should delete the failed token if it 403\'d', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
tokens: {
|
tokens: ImmutableMap({
|
||||||
'ABCDEFG': { token_type: 'Bearer', access_token: 'ABCDEFG' },
|
'ABCDEFG': AuthTokenRecord({ token_type: 'Bearer', access_token: 'ABCDEFG' }),
|
||||||
'HIJKLMN': { token_type: 'Bearer', access_token: 'HIJKLMN' },
|
'HIJKLMN': AuthTokenRecord({ token_type: 'Bearer', access_token: 'HIJKLMN' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'HIJKLMN': { token_type: 'Bearer', access_token: 'HIJKLMN' },
|
'HIJKLMN': AuthTokenRecord({ token_type: 'Bearer', access_token: 'HIJKLMN' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
|
@ -250,19 +250,19 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('tokens')).toEqual(expected);
|
expect(result.tokens).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete any users associated with the failed token', () => {
|
it('should delete any users associated with the failed token', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = ImmutableMap({
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
|
@ -272,17 +272,17 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('users')).toEqual(expected);
|
expect(result.users).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reassign `me` to the next in line', () => {
|
it('should reassign `me` to the next in line', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({
|
const state = ReducerRecord({
|
||||||
me: 'https://gleasonator.com/users/alex',
|
me: 'https://gleasonator.com/users/alex',
|
||||||
users: {
|
users: ImmutableMap({
|
||||||
'https://gleasonator.com/users/alex': { id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' },
|
'https://gleasonator.com/users/alex': AuthUserRecord({ id: '1234', access_token: 'ABCDEFG', url: 'https://gleasonator.com/users/alex' }),
|
||||||
'https://gleasonator.com/users/benis': { id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' },
|
'https://gleasonator.com/users/benis': AuthUserRecord({ id: '5678', access_token: 'HIJKLMN', url: 'https://gleasonator.com/users/benis' }),
|
||||||
},
|
}),
|
||||||
})));
|
});
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: VERIFY_CREDENTIALS_FAIL,
|
type: VERIFY_CREDENTIALS_FAIL,
|
||||||
|
@ -291,7 +291,7 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('me')).toEqual('https://gleasonator.com/users/benis');
|
expect(result.me).toEqual('https://gleasonator.com/users/benis');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -303,16 +303,16 @@ describe('auth reducer', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
expect(result.get('me')).toEqual('https://gleasonator.com/users/benis');
|
expect(result.me).toEqual('https://gleasonator.com/users/benis');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ME_FETCH_SKIP', () => {
|
describe('ME_FETCH_SKIP', () => {
|
||||||
it('sets `me` to null', () => {
|
it('sets `me` to null', () => {
|
||||||
const state = ReducerRecord(ImmutableMap(fromJS({ me: 'https://gleasonator.com/users/alex' })));
|
const state = ReducerRecord({ me: 'https://gleasonator.com/users/alex' });
|
||||||
const action = { type: ME_FETCH_SKIP };
|
const action = { type: ME_FETCH_SKIP };
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.get('me')).toEqual(null);
|
expect(result.me).toEqual(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ describe('auth reducer', () => {
|
||||||
data: require('soapbox/__fixtures__/mastodon_initial_state.json'),
|
data: require('soapbox/__fixtures__/mastodon_initial_state.json'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected = fromJS({
|
const expected = {
|
||||||
me: 'https://mastodon.social/@benis911',
|
me: 'https://mastodon.social/@benis911',
|
||||||
app: {},
|
app: {},
|
||||||
users: {
|
users: {
|
||||||
|
@ -342,10 +342,10 @@ describe('auth reducer', () => {
|
||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
expect(result).toEqual(expected);
|
expect(result.toJS()).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe('modal reducer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle MODAL_CLOSE', () => {
|
it('should handle MODAL_CLOSE', () => {
|
||||||
const state = ImmutableList([
|
const state = ImmutableList<any>([
|
||||||
ImmutableRecord({
|
ImmutableRecord({
|
||||||
modalType: 'type1',
|
modalType: 'type1',
|
||||||
modalProps: { props1: '1' },
|
modalProps: { props1: '1' },
|
||||||
|
|
|
@ -56,14 +56,17 @@ export interface ReducerAdminReport extends AdminReportRecord {
|
||||||
|
|
||||||
// Umm... based?
|
// Umm... based?
|
||||||
// https://itnext.io/typescript-extract-unpack-a-type-from-a-generic-baca7af14e51
|
// https://itnext.io/typescript-extract-unpack-a-type-from-a-generic-baca7af14e51
|
||||||
type InnerRecord<R> = R extends ImmutableRecord<infer TProps> ? TProps : never;
|
// type InnerRecord<R> = R extends ImmutableRecord<infer TProps> ? TProps : never;
|
||||||
|
|
||||||
type InnerState = InnerRecord<State>;
|
// type InnerState = InnerRecord<State>;
|
||||||
|
|
||||||
// Lol https://javascript.plainenglish.io/typescript-essentials-conditionally-filter-types-488705bfbf56
|
// Lol https://javascript.plainenglish.io/typescript-essentials-conditionally-filter-types-488705bfbf56
|
||||||
type FilterConditionally<Source, Condition> = Pick<Source, {[K in keyof Source]: Source[K] extends Condition ? K : never}[keyof Source]>;
|
// type FilterConditionally<Source, Condition> = Pick<Source, {[K in keyof Source]: Source[K] extends Condition ? K : never}[keyof Source]>;
|
||||||
|
|
||||||
|
// type SetKeys = keyof FilterConditionally<InnerState, ImmutableOrderedSet<string>>;
|
||||||
|
|
||||||
|
type SetKeys = 'openReports' | 'latestUsers' | 'awaitingApproval';
|
||||||
|
|
||||||
type SetKeys = keyof FilterConditionally<InnerState, ImmutableOrderedSet<string>>;
|
|
||||||
type APIReport = { id: string, state: string, statuses: any[] };
|
type APIReport = { id: string, state: string, statuses: any[] };
|
||||||
type APIUser = { id: string, email: string, nickname: string, registration_reason: string };
|
type APIUser = { id: string, email: string, nickname: string, registration_reason: string };
|
||||||
|
|
||||||
|
|
|
@ -22,32 +22,33 @@ import type { AnyAction } from 'redux';
|
||||||
import type { APIEntity, Account as AccountEntity } from 'soapbox/types/entities';
|
import type { APIEntity, Account as AccountEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
export const AuthAppRecord = ImmutableRecord({
|
export const AuthAppRecord = ImmutableRecord({
|
||||||
access_token: '',
|
access_token: null as string | null,
|
||||||
client_id: '',
|
client_id: null as string | null,
|
||||||
client_secret: '',
|
client_secret: null as string | null,
|
||||||
id: '',
|
id: null as string | null,
|
||||||
name: '',
|
name: null as string | null,
|
||||||
redirect_uri: '',
|
redirect_uri: null as string | null,
|
||||||
vapid_key: '',
|
token_type: null as string | null,
|
||||||
website: '',
|
vapid_key: null as string | null,
|
||||||
});
|
website: null as string | null,
|
||||||
|
|
||||||
export const AuthUserRecord = ImmutableRecord({
|
|
||||||
access_token: '',
|
|
||||||
id: '',
|
|
||||||
url: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AuthTokenRecord = ImmutableRecord({
|
export const AuthTokenRecord = ImmutableRecord({
|
||||||
access_token: '',
|
access_token: null as string | null,
|
||||||
account: '',
|
account: null as string | null,
|
||||||
created_at: null as number | null,
|
created_at: null as number | null,
|
||||||
expires_in: null as number | null,
|
expires_in: null as number | null,
|
||||||
id: null as number | null,
|
id: null as number | null,
|
||||||
me: '',
|
me: null as string | null,
|
||||||
refresh_token: null as string | null,
|
refresh_token: null as string | null,
|
||||||
scope: '',
|
scope: null as string | null,
|
||||||
token_type: '',
|
token_type: null as string | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AuthUserRecord = ImmutableRecord({
|
||||||
|
access_token: null as string | null,
|
||||||
|
id: null as string | null,
|
||||||
|
url: null as string | null,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ReducerRecord = ImmutableRecord({
|
export const ReducerRecord = ImmutableRecord({
|
||||||
|
@ -126,7 +127,6 @@ const setSessionUser = (state: State) => state.update('me', me => {
|
||||||
const migrateLegacy = (state: State) => {
|
const migrateLegacy = (state: State) => {
|
||||||
if (localState) return state;
|
if (localState) return state;
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
console.log(localStorage.getItem('soapbox:auth:app'));
|
|
||||||
const app = AuthAppRecord(JSON.parse(localStorage.getItem('soapbox:auth:app')!));
|
const app = AuthAppRecord(JSON.parse(localStorage.getItem('soapbox:auth:app')!));
|
||||||
const user = fromJS(JSON.parse(localStorage.getItem('soapbox:auth:user')!)) as ImmutableMap<string, any>;
|
const user = fromJS(JSON.parse(localStorage.getItem('soapbox:auth:user')!)) as ImmutableMap<string, any>;
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
@ -186,7 +186,6 @@ const persistState = (state: State) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialize = (state: State) => {
|
const initialize = (state: State) => {
|
||||||
console.log(JSON.stringify(state.toJS()), JSON.stringify(localState?.toJS()));
|
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
maybeShiftMe(state);
|
maybeShiftMe(state);
|
||||||
setSessionUser(state);
|
setSessionUser(state);
|
||||||
|
@ -199,7 +198,7 @@ const initialize = (state: State) => {
|
||||||
const initialState = initialize(ReducerRecord().merge(localState as any));
|
const initialState = initialize(ReducerRecord().merge(localState as any));
|
||||||
|
|
||||||
const importToken = (state: State, token: APIEntity) => {
|
const importToken = (state: State, token: APIEntity) => {
|
||||||
return state.setIn(['tokens', token.access_token], AuthTokenRecord(ImmutableMap(fromJS(token))));
|
return state.setIn(['tokens', token.access_token], AuthTokenRecord(token));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Upgrade the `_legacy` placeholder ID with a real account
|
// Upgrade the `_legacy` placeholder ID with a real account
|
||||||
|
@ -238,7 +237,7 @@ const userMismatch = (token: string, account: APIEntity) => {
|
||||||
|
|
||||||
const importCredentials = (state: State, token: string, account: APIEntity) => {
|
const importCredentials = (state: State, token: string, account: APIEntity) => {
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
state.setIn(['users', account.url], ImmutableMap({
|
state.setIn(['users', account.url], AuthUserRecord({
|
||||||
id: account.id,
|
id: account.id,
|
||||||
access_token: token,
|
access_token: token,
|
||||||
url: account.url,
|
url: account.url,
|
||||||
|
@ -261,7 +260,7 @@ const deleteToken = (state: State, token: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUser = (state: State, account: AccountEntity) => {
|
const deleteUser = (state: State, account: AccountEntity) => {
|
||||||
const accountUrl = account.url;
|
const accountUrl = account.get('url');
|
||||||
|
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
state.update('users', users => users.delete(accountUrl));
|
state.update('users', users => users.delete(accountUrl));
|
||||||
|
@ -272,24 +271,24 @@ const deleteUser = (state: State, account: AccountEntity) => {
|
||||||
|
|
||||||
const importMastodonPreload = (state: State, data: ImmutableMap<string, any>) => {
|
const importMastodonPreload = (state: State, data: ImmutableMap<string, any>) => {
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
const accountId = data.getIn(['meta', 'me']);
|
const accountId = data.getIn(['meta', 'me']) as string;
|
||||||
const accountUrl = data.getIn(['accounts', accountId, 'url']) as string;
|
const accountUrl = data.getIn(['accounts', accountId, 'url']) as string;
|
||||||
const accessToken = data.getIn(['meta', 'access_token']);
|
const accessToken = data.getIn(['meta', 'access_token']) as string;
|
||||||
|
|
||||||
if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) {
|
if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) {
|
||||||
state.setIn(['tokens', accessToken], AuthTokenRecord(ImmutableMap(fromJS({
|
state.setIn(['tokens', accessToken], AuthTokenRecord({
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
account: accountId,
|
account: accountId,
|
||||||
me: accountUrl,
|
me: accountUrl,
|
||||||
scope: 'read write follow push',
|
scope: 'read write follow push',
|
||||||
token_type: 'Bearer',
|
token_type: 'Bearer',
|
||||||
}))));
|
}));
|
||||||
|
|
||||||
state.setIn(['users', accountUrl], AuthUserRecord(ImmutableMap(fromJS({
|
state.setIn(['users', accountUrl], AuthUserRecord({
|
||||||
id: accountId,
|
id: accountId,
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
url: accountUrl,
|
url: accountUrl,
|
||||||
}))));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeShiftMe(state);
|
maybeShiftMe(state);
|
||||||
|
@ -322,10 +321,8 @@ const deleteForbiddenToken = (state: State, error: AxiosError, token: string) =>
|
||||||
const reducer = (state: State, action: AnyAction) => {
|
const reducer = (state: State, action: AnyAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case AUTH_APP_CREATED:
|
case AUTH_APP_CREATED:
|
||||||
console.log(action.app, AuthAppRecord(action.app));
|
|
||||||
return state.set('app', AuthAppRecord(action.app));
|
return state.set('app', AuthAppRecord(action.app));
|
||||||
case AUTH_APP_AUTHORIZED:
|
case AUTH_APP_AUTHORIZED:
|
||||||
console.log(state.app, state.app.merge(action.token));
|
|
||||||
return state.update('app', app => app.merge(action.token));
|
return state.update('app', app => app.merge(action.token));
|
||||||
case AUTH_LOGGED_IN:
|
case AUTH_LOGGED_IN:
|
||||||
return importToken(state, action.token);
|
return importToken(state, action.token);
|
||||||
|
|
|
@ -274,7 +274,7 @@ const getAuthUserIds = createSelector([
|
||||||
return authUsers.reduce((ids: ImmutableOrderedSet<string>, authUser) => {
|
return authUsers.reduce((ids: ImmutableOrderedSet<string>, authUser) => {
|
||||||
try {
|
try {
|
||||||
const id = authUser.get('id');
|
const id = authUser.get('id');
|
||||||
return validId(id) ? ids.add(id) : ids;
|
return validId(id) ? ids.add(id!) : ids;
|
||||||
} catch {
|
} catch {
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,6 @@ body,
|
||||||
var(--brand-color_s),
|
var(--brand-color_s),
|
||||||
calc(var(--brand-color_l) - 8%)
|
calc(var(--brand-color_l) - 8%)
|
||||||
);
|
);
|
||||||
--vignette-color: transparent;
|
|
||||||
|
|
||||||
// Meta-variables
|
// Meta-variables
|
||||||
--primary-text-color_h: 0;
|
--primary-text-color_h: 0;
|
||||||
|
@ -97,45 +96,4 @@ body,
|
||||||
var(--brand-color_s),
|
var(--brand-color_s),
|
||||||
calc(var(--brand-color_l) - 5%)
|
calc(var(--brand-color_l) - 5%)
|
||||||
);
|
);
|
||||||
--warning-color--hicontrast: hsl(
|
|
||||||
var(--warning-color_h),
|
|
||||||
var(--warning-color_s),
|
|
||||||
calc(var(--warning-color_l) - 12%)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-mode-dark {
|
|
||||||
// Primary variables
|
|
||||||
--highlight-text-color: hsl(
|
|
||||||
var(--brand-color_h),
|
|
||||||
var(--brand-color_s),
|
|
||||||
calc(var(--brand-color_l) + 8%)
|
|
||||||
);
|
|
||||||
--vignette-color: #000;
|
|
||||||
|
|
||||||
// Meta-variables
|
|
||||||
--primary-text-color_h: 0;
|
|
||||||
--primary-text-color_s: 0%;
|
|
||||||
--primary-text-color_l: 100%;
|
|
||||||
--background-color_h: 0;
|
|
||||||
--background-color_s: 0%;
|
|
||||||
--background-color_l: 20%;
|
|
||||||
--foreground-color_h: 0;
|
|
||||||
--foreground-color_s: 0%;
|
|
||||||
--foreground-color_l: 13%;
|
|
||||||
--warning-color_h: 0;
|
|
||||||
--warning-color_s: 100%;
|
|
||||||
--warning-color_l: 66%;
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
--brand-color--hicontrast: hsl(
|
|
||||||
var(--brand-color_h),
|
|
||||||
var(--brand-color_s),
|
|
||||||
calc(var(--brand-color_l) + 12%)
|
|
||||||
);
|
|
||||||
--warning-color--hicontrast: hsl(
|
|
||||||
var(--warning-color_h),
|
|
||||||
var(--warning-color_s),
|
|
||||||
calc(var(--warning-color_l) + 12%)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@
|
||||||
"html-webpack-harddisk-plugin": "^2.0.0",
|
"html-webpack-harddisk-plugin": "^2.0.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"http-link-header": "^1.0.2",
|
"http-link-header": "^1.0.2",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^4.2.1",
|
||||||
"imports-loader": "^4.0.0",
|
"imports-loader": "^4.0.0",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "^0.12.0",
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
|
|
|
@ -6741,10 +6741,10 @@ immer@^9.0.7:
|
||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20"
|
||||||
integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==
|
integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==
|
||||||
|
|
||||||
immutable@^4.0.0:
|
immutable@^4.2.1:
|
||||||
version "4.0.0"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.1.tgz#8a4025691018c560a40c67e43d698f816edc44d4"
|
||||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
integrity sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==
|
||||||
|
|
||||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
|
|
Ładowanie…
Reference in New Issue