support instanceV2

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
instancev2
marcin mikołajczak 2022-12-04 19:59:31 +01:00
rodzic 0d42fe1c96
commit 6ecb870c88
20 zmienionych plików z 330 dodań i 85 usunięć

Wyświetl plik

@ -0,0 +1,128 @@
{
"domain": "mastodon.social",
"title": "Mastodon",
"version": "4.0.2",
"source_url": "https://github.com/mastodon/mastodon",
"description": "The original server operated by the Mastodon gGmbH non-profit",
"usage": { "users": { "active_month": 227900 } },
"thumbnail": {
"url": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
"blurhash": "UeKUpFxuo~R%0nW;WCnhF6RjaJt757oJodS$",
"versions": {
"@1x": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
"@2x": "https://files.mastodon.social/site_uploads/files/000/000/001/@2x/57c12f441d083cde.png"
}
},
"languages": ["en"],
"configuration": {
"urls": { "streaming": "wss://mastodon.social" },
"accounts": { "max_featured_tags": 10 },
"statuses": {
"max_characters": 500,
"max_media_attachments": 4,
"characters_reserved_per_url": 23
},
"media_attachments": {
"supported_mime_types": [
"image/jpeg",
"image/png",
"image/gif",
"image/heic",
"image/heif",
"image/webp",
"image/avif",
"video/webm",
"video/mp4",
"video/quicktime",
"video/ogg",
"audio/wave",
"audio/wav",
"audio/x-wav",
"audio/x-pn-wave",
"audio/vnd.wave",
"audio/ogg",
"audio/vorbis",
"audio/mpeg",
"audio/mp3",
"audio/webm",
"audio/flac",
"audio/aac",
"audio/m4a",
"audio/x-m4a",
"audio/mp4",
"audio/3gpp",
"video/x-ms-asf"
],
"image_size_limit": 10485760,
"image_matrix_limit": 16777216,
"video_size_limit": 41943040,
"video_frame_rate_limit": 60,
"video_matrix_limit": 2304000
},
"polls": {
"max_options": 4,
"max_characters_per_option": 50,
"min_expiration": 300,
"max_expiration": 2629746
},
"translation": { "enabled": true }
},
"registrations": {
"enabled": false,
"approval_required": false,
"message": null
},
"contact": {
"email": "staff@mastodon.social",
"account": {
"id": "1",
"username": "Gargron",
"acct": "Gargron",
"display_name": "Eugen Rochko",
"locked": false,
"bot": false,
"discoverable": true,
"group": false,
"created_at": "2016-03-16T00:00:00.000Z",
"note": "\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e",
"url": "https://mastodon.social/@Gargron",
"avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
"avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
"header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
"header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
"followers_count": 263431,
"following_count": 327,
"statuses_count": 72827,
"last_status_at": "2022-12-03",
"noindex": false,
"emojis": [],
"fields": [
{
"name": "Patreon",
"value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
"verified_at": null
}
]
}
},
"rules": [
{
"id": "1",
"text": "Sexually explicit or violent media must be marked as sensitive when posting"
},
{
"id": "2",
"text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism"
},
{
"id": "3",
"text": "No incitement of violence or promotion of violent ideologies"
},
{ "id": "4", "text": "No harassment, dogpiling or doxxing of other users" },
{ "id": "5", "text": "No content illegal in Germany" },
{
"id": "7",
"text": "Do not share intentionally false or misleading information"
}
]
}

Wyświetl plik

@ -77,7 +77,7 @@ const externalAuthorize = (instance: Instance, baseURL: string) =>
const externalEthereumLogin = (instance: Instance, baseURL?: string) =>
(dispatch: AppDispatch) => {
const loginMessage = instance.login_message;
const loginMessage = instance.registrations.get('message');
return getWalletAndSign(loginMessage).then(({ wallet, signature }) => {
return dispatch(createExternalApp(instance, baseURL)).then((app) => {

Wyświetl plik

@ -1,10 +1,11 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import get from 'lodash/get';
import { gte } from 'semver';
import KVStore from 'soapbox/storage/kv-store';
import { RootState } from 'soapbox/store';
import { getAuthUserUrl } from 'soapbox/utils/auth';
import { parseVersion } from 'soapbox/utils/features';
import { MASTODON, parseVersion, PLEROMA, REBASED } from 'soapbox/utils/features';
import api from '../api';
@ -27,25 +28,48 @@ export const getHost = (state: RootState) => {
export const rememberInstance = createAsyncThunk(
'instance/remember',
async(host: string) => {
return await KVStore.getItemOrError(`instance:${host}`);
const instance = await KVStore.getItemOrError(`instance:${host}`);
return { instance, host };
},
);
const supportsInstanceV2 = (instance: Record<string, any>): boolean => {
const v = parseVersion(get(instance, 'version'));
return (v.software === MASTODON && gte(v.compatVersion, '4.0.0')) ||
(v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'));
};
/** We may need to fetch nodeinfo on Pleroma < 2.1 */
const needsNodeinfo = (instance: Record<string, any>): boolean => {
const v = parseVersion(get(instance, 'version'));
return v.software === 'Pleroma' && !get(instance, ['pleroma', 'metadata']);
return v.software === PLEROMA && !get(instance, ['pleroma', 'metadata']);
};
export const fetchInstance = createAsyncThunk<void, void, { state: RootState }>(
export const fetchInstance = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
'instance/fetch',
async(_arg, { dispatch, getState, rejectWithValue }) => {
async(host, { dispatch, getState, rejectWithValue }) => {
try {
const { data: instance } = await api(getState).get('/api/v1/instance');
if (supportsInstanceV2(instance)) {
return dispatch(fetchInstanceV2(host)) as any as { instance: Record<string, any>, host?: string | null };
}
if (needsNodeinfo(instance)) {
dispatch(fetchNodeinfo());
}
return instance;
return { instance, host };
} catch (e) {
return rejectWithValue(e);
}
},
);
export const fetchInstanceV2 = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
'instance/fetch',
async(host, { getState, rejectWithValue }) => {
try {
const { data: instance } = await api(getState).get('/api/v2/instance');
return { instance, host };
} catch (e) {
return rejectWithValue(e);
}
@ -57,10 +81,11 @@ export const loadInstance = createAsyncThunk<void, void, { state: RootState }>(
'instance/load',
async(_arg, { dispatch, getState }) => {
const host = getHost(getState());
await Promise.all([
dispatch(rememberInstance(host || '')),
dispatch(fetchInstance()),
]);
const rememberedInstance = await dispatch(rememberInstance(host || ''));
if (rememberedInstance.payload && supportsInstanceV2((rememberedInstance.payload as any).instance)) {
await dispatch(fetchInstanceV2(host));
} else dispatch(fetchInstance(host));
},
);

Wyświetl plik

@ -143,7 +143,7 @@ const SoapboxMount = () => {
<Route exact path='/about/:slug?' component={PublicLayout} />
<Route path='/login' component={AuthLayout} />
{(features.accountCreation && instance.registrations) && (
{(features.accountCreation && instance.registrations.get('enabled')) && (
<Route exact path='/signup' component={AuthLayout} />
)}

Wyświetl plik

@ -34,8 +34,8 @@ const generateConfig = (mode: RegistrationMode) => {
};
const modeFromInstance = (instance: Instance): RegistrationMode => {
if (instance.approval_required && instance.registrations) return 'approval';
return instance.registrations ? 'open' : 'closed';
if (instance.registrations.get('approval_required') && instance.registrations.get('enabled')) return 'approval';
return instance.registrations.get('enabled') ? 'open' : 'closed';
};
/** Allows changing the registration mode of the instance, eg "open", "closed", "approval" */

Wyświetl plik

@ -74,7 +74,7 @@ const Ad: React.FC<IAd> = ({ ad }) => {
<Card className='py-6 sm:p-5' variant='rounded'>
<Stack space={4}>
<HStack alignItems='center' space={3}>
<Avatar src={instance.thumbnail} size={42} />
<Avatar src={instance.thumbnail.get('url')} size={42} />
<Stack grow>
<HStack space={1}>

Wyświetl plik

@ -32,7 +32,7 @@ const AuthLayout = () => {
const soapboxConfig = useSoapboxConfig();
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
const isOpen = features.accountCreation && instance.registrations;
const isOpen = features.accountCreation && instance.registrations.get('enabled');
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
const isLoginPage = history.location.pathname === '/login';
const shouldShowRegisterLink = (isLoginPage && (isOpen || (pepeEnabled && pepeOpen)));

Wyświetl plik

@ -46,7 +46,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const locale = settings.get('locale');
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
const needsApproval = instance.approval_required;
const needsApproval = instance.registrations.get('approval_required');
const supportsEmailList = features.emailList;
const supportsAccountLookup = features.accountLookup;
const birthdayRequired = instance.pleroma.getIn(['metadata', 'birthday_required']);

Wyświetl plik

@ -70,7 +70,9 @@ const Upload: React.FC<IUpload> = ({ composeId, id }) => {
const intl = useIntl();
const history = useHistory();
const dispatch = useAppDispatch();
const { description_limit: descriptionLimit } = useInstance();
const { pleroma } = useInstance();
const descriptionLimit = pleroma.getIn(['metadata', 'description_limit']) as number;
const media = useCompose(composeId).media_attachments.find(item => item.id === id)!;

Wyświetl plik

@ -12,8 +12,10 @@ describe('<LandingPage />', () => {
const state = rootReducer(undefined, {
type: rememberInstance.fulfilled.type,
payload: {
version: '2.7.2 (compatible; Pleroma 2.3.0)',
registrations: true,
instance: {
version: '2.7.2 (compatible; Pleroma 2.3.0)',
registrations: true,
},
},
});
@ -29,8 +31,10 @@ describe('<LandingPage />', () => {
const state = rootReducer(undefined, {
type: rememberInstance.fulfilled.type,
payload: {
version: '2.7.2 (compatible; Pleroma 2.3.0)',
registrations: false,
instance: {
version: '2.7.2 (compatible; Pleroma 2.3.0)',
registrations: false,
},
},
});
@ -71,8 +75,10 @@ describe('<LandingPage />', () => {
const state = applyActions(undefined, [{
type: rememberInstance.fulfilled.type,
payload: {
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
registrations: false,
instance: {
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
registrations: false,
},
},
}, {
type: PEPE_FETCH_INSTANCE_SUCCESS,

Wyświetl plik

@ -89,13 +89,15 @@ const LandingPage = () => {
);
};
console.log(instance.registrations.toJS());
// Render registration flow depending on features
const renderBody = () => {
if (soapboxConfig.authProvider) {
return renderProvider();
} else if (pepeEnabled && pepeOpen) {
return renderPepe();
} else if (features.accountCreation && instance.registrations) {
} else if (features.accountCreation && instance.registrations.get('enabled')) {
return renderOpen();
} else {
return renderClosed();
@ -115,7 +117,7 @@ const LandingPage = () => {
<Markup
size='lg'
dangerouslySetInnerHTML={{ __html: instance.short_description || instance.description }}
dangerouslySetInnerHTML={{ __html: instance.description }}
/>
</Stack>
</div>

Wyświetl plik

@ -35,7 +35,7 @@ const Header = () => {
const features = useFeatures();
const instance = useInstance();
const isOpen = features.accountCreation && instance.registrations;
const isOpen = features.accountCreation && instance.registrations.get('enabled');
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
const [isLoading, setLoading] = React.useState(false);

Wyświetl plik

@ -28,7 +28,7 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
const instance = useInstance();
const features = useFeatures();
const isOpen = features.accountCreation && instance.registrations;
const isOpen = features.accountCreation && instance.registrations.get('enabled');
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
return (

Wyświetl plik

@ -335,7 +335,7 @@ const UI: React.FC = ({ children }) => {
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.openId !== null);
const accessToken = useAppSelector(state => getAccessToken(state));
const streamingUrl = instance.urls.get('streaming_api');
const streamingUrl = instance.configuration.getIn(['urls', 'streaming_api']);
const standalone = useAppSelector(isStandalone);
const handleDragEnter = (e: DragEvent) => {

Wyświetl plik

@ -5,8 +5,6 @@ import { normalizeInstance } from '../instance';
describe('normalizeInstance()', () => {
it('normalizes an empty Map', () => {
const expected = {
approval_required: false,
contact_account: {},
configuration: {
media_attachments: {},
polls: {
@ -19,20 +17,29 @@ describe('normalizeInstance()', () => {
max_characters: 500,
max_media_attachments: 4,
},
translation: {
enabled: false,
},
urls: {
streaming: '',
},
},
contact: {
account: {},
email: '',
},
description: '',
description_limit: 1500,
domain: '',
email: '',
feature_quote: false,
fedibird_capabilities: [],
invites_enabled: false,
languages: [],
login_message: '',
pleroma: {
metadata: {
account_activation_required: false,
birthday_min_age: 0,
birthday_required: false,
description_limit: 1500,
features: [],
federation: {
enabled: true,
@ -41,18 +48,23 @@ describe('normalizeInstance()', () => {
},
stats: {},
},
registrations: false,
rules: [],
short_description: '',
stats: {
domain_count: 0,
status_count: 0,
user_count: 0,
registrations: {
approval_required: false,
enabled: false,
message: '',
},
rules: [],
source_url: '',
stats: {},
title: '',
thumbnail: '',
uri: '',
urls: {},
thumbnail: {
url: '',
},
usage: {
users: {
active_month: 0,
},
},
version: '0.0.0',
};
@ -139,7 +151,7 @@ describe('normalizeInstance()', () => {
const result = normalizeInstance(instance);
// Sets description_limit
expect(result.description_limit).toEqual(1500);
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toEqual(1500);
// Preserves fedibird_capabilities
expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities));
@ -151,7 +163,7 @@ describe('normalizeInstance()', () => {
// Adds configuration and description_limit
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
expect(result.get('description_limit')).toBe(1500);
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
});
it('normalizes GoToSocial instance', () => {
@ -164,7 +176,7 @@ describe('normalizeInstance()', () => {
// Adds configuration and description_limit
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
expect(result.get('description_limit')).toBe(1500);
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
});
it('normalizes Friendica instance', () => {
@ -177,7 +189,7 @@ describe('normalizeInstance()', () => {
// Adds configuration and description_limit
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
expect(result.get('description_limit')).toBe(1500);
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
});
it('normalizes a Mastodon RC version', () => {

Wyświetl plik

@ -17,8 +17,10 @@ import { isNumber } from 'soapbox/utils/numbers';
// Use Mastodon defaults
// https://docs.joinmastodon.org/entities/instance/
export const InstanceRecord = ImmutableRecord({
approval_required: false,
contact_account: ImmutableMap<string, any>(),
contact: ImmutableMap<string, any>({
account: ImmutableMap<string, any>(),
email: '',
}),
configuration: ImmutableMap<string, any>({
media_attachments: ImmutableMap<string, any>(),
polls: ImmutableMap<string, number>({
@ -31,15 +33,31 @@ export const InstanceRecord = ImmutableRecord({
max_characters: 500,
max_media_attachments: 4,
}),
translation: ImmutableMap<string, any>({ enabled: false }),
urls: ImmutableMap<string, string>(),
}),
description: '',
description_limit: 1500,
domain: '',
email: '',
feature_quote: false,
fedibird_capabilities: ImmutableList(),
invites_enabled: false,
languages: ImmutableList(),
login_message: '',
registrations: ImmutableMap<string, any>({
approval_required: false,
enabled: false,
message: '',
}),
rules: ImmutableList(),
source_url: '',
stats: ImmutableMap<string, number>(),
title: '',
thumbnail: ImmutableMap<string, any>(),
usage: ImmutableMap<string, any>({
users: ImmutableMap<string, number>({
active_month: 0,
}),
}),
version: '0.0.0',
pleroma: ImmutableMap<string, any>({
metadata: ImmutableMap<string, any>({
account_activation_required: false,
@ -50,22 +68,13 @@ export const InstanceRecord = ImmutableRecord({
enabled: true,
exclusions: false,
}),
description_limit: 1500,
}),
stats: ImmutableMap(),
}),
registrations: false,
rules: ImmutableList(),
short_description: '',
stats: ImmutableMap<string, number>({
domain_count: 0,
status_count: 0,
user_count: 0,
}),
title: '',
thumbnail: '',
uri: '',
urls: ImmutableMap<string, string>(),
version: '0.0.0',
feature_quote: false,
fedibird_capabilities: ImmutableList(),
});
// Build Mastodon configuration from Pleroma instance
@ -109,10 +118,39 @@ const fixAkkoma = (instance: ImmutableMap<string, any>) => {
}
};
const fixInstanceV1 = (instance: ImmutableMap<string, any>) => {
instance.setIn(['configuration', 'urls', 'streaming'], instance.getIn(['urls', 'streaming_api'], ''));
instance.set('contact', ImmutableMap({
account: instance.get('contact_account'),
email: instance.get('email'),
}));
instance.set('description', instance.get('short_description') || instance.get('description'));
instance.set('registrations', ImmutableMap({
approval_required: instance.get('approval_required'),
enabled: instance.get('registrations'),
message: instance.get('login_message'),
}));
instance.set('thumbnail', ImmutableMap({
url: instance.get('thumbnail', ''),
}));
if (instance.has('pleroma')) {
instance.setIn(['pleroma', 'metadata', 'description_limit'], instance.get('description_limit'));
}
};
// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
export const normalizeInstance = (instance: Record<string, any>) => {
return InstanceRecord(
ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap<string, any>) => {
const version = instance.has('domain') ? 'v2' : 'v1';
if (version === 'v1') fixInstanceV1(instance);
const { software } = parseVersion(instance.get('version'));
const mastodonConfig = pleromaToMastodonConfig(instance);

Wyświetl plik

@ -10,7 +10,6 @@ describe('instance reducer', () => {
const result = reducer(undefined, {} as any);
const expected = {
description_limit: 1500,
configuration: {
statuses: {
max_characters: 500,
@ -34,7 +33,7 @@ describe('instance reducer', () => {
it('normalizes Pleroma instance with Mastodon configuration format', () => {
const action = {
type: rememberInstance.fulfilled.type,
payload: require('soapbox/__fixtures__/pleroma-instance.json'),
payload: { instance: require('soapbox/__fixtures__/pleroma-instance.json') },
};
const result = reducer(undefined, action);
@ -60,7 +59,7 @@ describe('instance reducer', () => {
it('normalizes Mastodon instance with retained configuration', () => {
const action = {
type: rememberInstance.fulfilled.type,
payload: require('soapbox/__fixtures__/mastodon-instance.json'),
payload: { instance: require('soapbox/__fixtures__/mastodon-instance.json') },
};
const result = reducer(undefined, action);
@ -94,7 +93,7 @@ describe('instance reducer', () => {
it('normalizes Mastodon 3.0.0 instance with default configuration', () => {
const action = {
type: rememberInstance.fulfilled.type,
payload: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json'),
payload: { instance: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json') },
};
const result = reducer(undefined, action);
@ -116,6 +115,40 @@ describe('instance reducer', () => {
expect(result.toJS()).toMatchObject(expected);
});
it('normalizes Mastodon 4.0.2 instance fetched with v2 endpoint', () => {
const action = {
type: rememberInstance.fulfilled.type,
payload: { instance: require('soapbox/__fixtures__/mastodon-instance-v2.json') },
};
const result = reducer(undefined, action);
const expected = {
configuration: {
statuses: {
max_characters: 500,
max_media_attachments: 4,
},
polls: {
max_options: 4,
max_characters_per_option: 50,
min_expiration: 300,
max_expiration: 2629746,
},
translation: {
enabled: true,
},
},
registrations: {
enabled: false,
approval_required: false,
message: null,
},
};
expect(result.toJS()).toMatchObject(expected);
});
});
describe('ADMIN_CONFIG_UPDATE_REQUEST', () => {
@ -127,13 +160,13 @@ describe('instance reducer', () => {
configs,
};
// The normalizer has `registrations: closed` by default
// The normalizer has `registrations.enabled: closed` by default
const state = reducer(undefined, {} as any);
expect(state.registrations).toBe(false);
expect(state.registrations.get('enabled')).toBe(false);
// After importing the configs, registration will be open
const result = reducer(state, action);
expect(result.registrations).toBe(true);
expect(result.registrations.get('enabled')).toBe(true);
});
});
});

Wyświetl plik

@ -65,8 +65,8 @@ const importConfigs = (state: typeof initialState, configs: ImmutableList<any>)
const registrationsOpen = getConfigValue(value, ':registrations_open');
const approvalRequired = getConfigValue(value, ':account_approval_required');
state.update('registrations', c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
state.update('approval_required', c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
state.updateIn(['registrations', 'enabled'], c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
state.updateIn(['registrations', 'approval_required'], c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
}
if (simplePolicy) {
@ -95,8 +95,7 @@ const getHost = (instance: { uri: string }) => {
}
};
const persistInstance = (instance: { uri: string }) => {
const host = getHost(instance);
const persistInstance = (instance: { uri: string }, host: string | null = getHost(instance)) => {
if (host) {
KVStore.setItem(`instance:${host}`, instance).catch(console.error);
@ -116,14 +115,14 @@ export default function instance(state = initialState, action: AnyAction) {
case PLEROMA_PRELOAD_IMPORT:
return preloadImport(state, action, '/api/v1/instance');
case rememberInstance.fulfilled.type:
return importInstance(state, ImmutableMap(fromJS(action.payload)));
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
case fetchInstance.fulfilled.type:
persistInstance(action.payload);
return importInstance(state, ImmutableMap(fromJS(action.payload)));
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
case fetchInstance.rejected.type:
return handleInstanceFetchFail(state, action.error);
case fetchNodeinfo.fulfilled.type:
return importNodeinfo(state, ImmutableMap(fromJS(action.payload)));
return importNodeinfo(state, ImmutableMap(fromJS(action.payload.instance)));
case ADMIN_CONFIG_UPDATE_REQUEST:
case ADMIN_CONFIG_UPDATE_SUCCESS:
return importConfigs(state, ImmutableList(fromJS(action.configs)));

Wyświetl plik

@ -14,7 +14,7 @@ export function connectStream(
callbacks: (dispatch: AppDispatch, getState: () => RootState) => Record<string, any> = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} }),
) {
return (dispatch: AppDispatch, getState: () => RootState) => {
const streamingAPIBaseURL = getState().instance.urls.get('streaming_api');
const streamingAPIBaseURL = getState().instance.configuration.getIn(['urls', 'streaming']) as string;
const accessToken = getAccessToken(getState());
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
@ -40,7 +40,7 @@ export function connectStream(
// If the WebSocket fails to be created, don't crash the whole page,
// just proceed without a subscription.
try {
subscription = getStream(streamingAPIBaseURL!, accessToken, path, {
subscription = getStream(streamingAPIBaseURL, accessToken, path, {
connected() {
if (pollingRefresh) {
clearPolling();

Wyświetl plik

@ -628,7 +628,7 @@ const getInstanceFeatures = (instance: Instance) => {
* Can translate statuses.
* @see POST /api/v1/statuses/:id/translate
*/
translations: features.includes('translation'),
translations: features.includes('translation') || instance.configuration.getIn(['translation', 'enabled']),
/**
* Trending statuses.