Make useInstance hook call V2 instance, fallback to V1 instance upgraded
This commit is contained in:
parent
5966f9efae
commit
ba4ab3d39a
|
@ -1,5 +1,4 @@
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getNextLinkName } from 'soapbox/utils/quirks';
|
|
||||||
|
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
|
@ -18,14 +17,13 @@ const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';
|
||||||
|
|
||||||
const fetchBlocks = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
const fetchBlocks = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return null;
|
if (!isLoggedIn(getState)) return null;
|
||||||
const nextLinkName = getNextLinkName(getState);
|
|
||||||
|
|
||||||
dispatch(fetchBlocksRequest());
|
dispatch(fetchBlocksRequest());
|
||||||
|
|
||||||
return api(getState)
|
return api(getState)
|
||||||
.get('/api/v1/blocks')
|
.get('/api/v1/blocks')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedAccounts(response.data));
|
dispatch(importFetchedAccounts(response.data));
|
||||||
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
|
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
|
||||||
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
||||||
|
@ -54,7 +52,6 @@ function fetchBlocksFail(error: unknown) {
|
||||||
|
|
||||||
const expandBlocks = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
const expandBlocks = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return null;
|
if (!isLoggedIn(getState)) return null;
|
||||||
const nextLinkName = getNextLinkName(getState);
|
|
||||||
|
|
||||||
const url = getState().user_lists.blocks.next;
|
const url = getState().user_lists.blocks.next;
|
||||||
|
|
||||||
|
@ -67,7 +64,7 @@ const expandBlocks = () => (dispatch: AppDispatch, getState: () => RootState) =>
|
||||||
return api(getState)
|
return api(getState)
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedAccounts(response.data));
|
dispatch(importFetchedAccounts(response.data));
|
||||||
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));
|
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));
|
||||||
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
||||||
|
|
|
@ -9,10 +9,9 @@
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
import { instanceSchema, type Instance } from 'soapbox/schemas';
|
import { InstanceV1, instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
import { parseBaseURL } from 'soapbox/utils/auth';
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
import { getQuirks } from 'soapbox/utils/quirks';
|
|
||||||
import { getInstanceScopes } from 'soapbox/utils/scopes';
|
import { getInstanceScopes } from 'soapbox/utils/scopes';
|
||||||
|
|
||||||
import { baseClient } from '../api';
|
import { baseClient } from '../api';
|
||||||
|
@ -22,36 +21,33 @@ import type { AppDispatch, RootState } from 'soapbox/store';
|
||||||
const fetchExternalInstance = (baseURL?: string) => {
|
const fetchExternalInstance = (baseURL?: string) => {
|
||||||
return baseClient(null, baseURL)
|
return baseClient(null, baseURL)
|
||||||
.get('/api/v1/instance')
|
.get('/api/v1/instance')
|
||||||
.then(({ data: instance }) => instanceSchema.parse(instance))
|
.then(({ data: instance }) => instanceV1Schema.parse(instance))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// Authenticated fetch is enabled.
|
// Authenticated fetch is enabled.
|
||||||
// Continue with a limited featureset.
|
// Continue with a limited featureset.
|
||||||
return instanceSchema.parse({});
|
return instanceV1Schema.parse({});
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createExternalApp = (instance: Instance, baseURL?: string) =>
|
const createExternalApp = (instance: InstanceV1, baseURL?: string) =>
|
||||||
(dispatch: AppDispatch, _getState: () => RootState) => {
|
(dispatch: AppDispatch, _getState: () => RootState) => {
|
||||||
// Mitra: skip creating the auth app
|
|
||||||
if (getQuirks(instance).noApps) return new Promise(f => f({}));
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
client_name: sourceCode.displayName,
|
client_name: sourceCode.displayName,
|
||||||
redirect_uris: `${window.location.origin}/login/external`,
|
redirect_uris: `${window.location.origin}/login/external`,
|
||||||
website: sourceCode.homepage,
|
website: sourceCode.homepage,
|
||||||
scopes: getInstanceScopes(instance),
|
scopes: getInstanceScopes(instance.version),
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(createApp(params, baseURL));
|
return dispatch(createApp(params, baseURL));
|
||||||
};
|
};
|
||||||
|
|
||||||
const externalAuthorize = (instance: Instance, baseURL: string) =>
|
const externalAuthorize = (instance: InstanceV1, baseURL: string) =>
|
||||||
(dispatch: AppDispatch, _getState: () => RootState) => {
|
(dispatch: AppDispatch, _getState: () => RootState) => {
|
||||||
const scopes = getInstanceScopes(instance);
|
const scopes = getInstanceScopes(instance.version);
|
||||||
|
|
||||||
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
|
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
|
||||||
const { client_id, redirect_uri } = app as Record<string, string>;
|
const { client_id, redirect_uri } = app as Record<string, string>;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema, instanceV2Schema } from 'soapbox/schemas/instance';
|
||||||
import { RootState } from 'soapbox/store';
|
import { RootState } from 'soapbox/store';
|
||||||
import { getAuthUserUrl, getMeUrl } from 'soapbox/utils/auth';
|
import { getAuthUserUrl, getMeUrl } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
@ -28,7 +28,7 @@ export const fetchInstance = createAsyncThunk<InstanceData, InstanceData['host']
|
||||||
async(host, { dispatch, getState, rejectWithValue }) => {
|
async(host, { dispatch, getState, rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api(getState).get('/api/v1/instance');
|
const { data } = await api(getState).get('/api/v1/instance');
|
||||||
const instance = instanceSchema.parse(data);
|
const instance = instanceV1Schema.parse(data);
|
||||||
const features = getFeatures(instance);
|
const features = getFeatures(instance);
|
||||||
|
|
||||||
if (features.instanceV2) {
|
if (features.instanceV2) {
|
||||||
|
@ -46,7 +46,8 @@ export const fetchInstanceV2 = createAsyncThunk<InstanceData, InstanceData['host
|
||||||
'instanceV2/fetch',
|
'instanceV2/fetch',
|
||||||
async(host, { getState, rejectWithValue }) => {
|
async(host, { getState, rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const { data: instance } = await api(getState).get('/api/v2/instance');
|
const { data } = await api(getState).get('/api/v2/instance');
|
||||||
|
const instance = instanceV2Schema.parse(data);
|
||||||
return { instance, host };
|
return { instance, host };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return rejectWithValue(e);
|
return rejectWithValue(e);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { __stub } from 'soapbox/api';
|
import { __stub } from 'soapbox/api';
|
||||||
import { buildGroup } from 'soapbox/jest/factory';
|
import { buildGroup } from 'soapbox/jest/factory';
|
||||||
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import { useGroups } from './useGroups';
|
import { useGroups } from './useGroups';
|
||||||
|
|
||||||
const group = buildGroup({ id: '1', display_name: 'soapbox' });
|
const group = buildGroup({ id: '1', display_name: 'soapbox' });
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { __stub } from 'soapbox/api';
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
import { buildAccount, buildGroup } from 'soapbox/jest/factory';
|
import { buildAccount, buildGroup } from 'soapbox/jest/factory';
|
||||||
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import { usePendingGroups } from './usePendingGroups';
|
import { usePendingGroups } from './usePendingGroups';
|
||||||
|
|
||||||
const id = '1';
|
const id = '1';
|
||||||
const group = buildGroup({ id, display_name: 'soapbox' });
|
const group = buildGroup({ id, display_name: 'soapbox' });
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
||||||
}),
|
}),
|
||||||
me: '1',
|
me: '1',
|
||||||
|
|
|
@ -3,17 +3,27 @@ import { useQuery } from '@tanstack/react-query';
|
||||||
import { useApi } from 'soapbox/hooks';
|
import { useApi } from 'soapbox/hooks';
|
||||||
import { InstanceV1, instanceV1Schema } from 'soapbox/schemas/instance';
|
import { InstanceV1, instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
|
interface Opts {
|
||||||
|
/** The base URL of the instance. */
|
||||||
|
baseUrl?: string;
|
||||||
|
/** Whether to fetch the instance from the API. */
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/** Get the Instance for the current backend. */
|
/** Get the Instance for the current backend. */
|
||||||
export function useInstanceV1() {
|
export function useInstanceV1(opts: Opts = {}) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
|
const { baseUrl, enabled } = opts;
|
||||||
|
|
||||||
const { data: instance, ...rest } = useQuery<InstanceV1>({
|
const { data: instance, ...rest } = useQuery<InstanceV1>({
|
||||||
queryKey: ['instance', api.baseUrl, 'v1'],
|
queryKey: ['instance', baseUrl ?? api.baseUrl, 'v1'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await api.get('/api/v1/instance');
|
const response = await api.get('/api/v1/instance');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return instanceV1Schema.parse(data);
|
return instanceV1Schema.parse(data);
|
||||||
},
|
},
|
||||||
|
enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { instance, ...rest };
|
return { instance, ...rest };
|
||||||
|
|
|
@ -3,17 +3,27 @@ import { useQuery } from '@tanstack/react-query';
|
||||||
import { useApi } from 'soapbox/hooks';
|
import { useApi } from 'soapbox/hooks';
|
||||||
import { InstanceV2, instanceV2Schema } from 'soapbox/schemas/instance';
|
import { InstanceV2, instanceV2Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
|
interface Opts {
|
||||||
|
/** The base URL of the instance. */
|
||||||
|
baseUrl?: string;
|
||||||
|
/** Whether to fetch the instance from the API. */
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/** Get the Instance for the current backend. */
|
/** Get the Instance for the current backend. */
|
||||||
export function useInstanceV2() {
|
export function useInstanceV2(opts: Opts = {}) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
|
const { baseUrl, enabled } = opts;
|
||||||
|
|
||||||
const { data: instance, ...rest } = useQuery<InstanceV2>({
|
const { data: instance, ...rest } = useQuery<InstanceV2>({
|
||||||
queryKey: ['instance', api.baseUrl, 'v2'],
|
queryKey: ['instance', baseUrl ?? api.baseUrl, 'v2'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await api.get('/api/v2/instance');
|
const response = await api.get('/api/v2/instance');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return instanceV2Schema.parse(data);
|
return instanceV2Schema.parse(data);
|
||||||
},
|
},
|
||||||
|
enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { instance, ...rest };
|
return { instance, ...rest };
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import { updateConfig } from 'soapbox/actions/admin';
|
import { updateConfig } from 'soapbox/actions/admin';
|
||||||
import { RadioGroup, RadioItem } from 'soapbox/components/radio';
|
import { RadioGroup, RadioItem } from 'soapbox/components/radio';
|
||||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||||
import { Instance } from 'soapbox/schemas';
|
import { InstanceV2 } from 'soapbox/schemas/instance';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
type RegistrationMode = 'open' | 'approval' | 'closed';
|
type RegistrationMode = 'open' | 'approval' | 'closed';
|
||||||
|
@ -27,7 +27,7 @@ const generateConfig = (mode: RegistrationMode) => {
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
|
|
||||||
const modeFromInstance = ({ registrations }: Instance): RegistrationMode => {
|
const modeFromInstance = ({ registrations }: InstanceV2): RegistrationMode => {
|
||||||
if (registrations.approval_required && registrations.enabled) return 'approval';
|
if (registrations.approval_required && registrations.enabled) return 'approval';
|
||||||
return registrations.enabled ? 'open' : 'closed';
|
return registrations.enabled ? 'open' : 'closed';
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,10 @@ import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email-list';
|
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email-list';
|
||||||
|
import { useInstanceV1 } from 'soapbox/api/hooks/instance/useInstanceV1';
|
||||||
import List, { ListItem } from 'soapbox/components/list';
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
import { CardTitle, Icon, IconButton, Stack } from 'soapbox/components/ui';
|
import { CardTitle, Icon, IconButton, Stack } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
import { download } from 'soapbox/utils/download';
|
import { download } from 'soapbox/utils/download';
|
||||||
import { parseVersion } from 'soapbox/utils/features';
|
import { parseVersion } from 'soapbox/utils/features';
|
||||||
|
@ -14,7 +15,7 @@ import RegistrationModePicker from '../components/registration-mode-picker';
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstanceV1();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
|
|
||||||
|
@ -39,15 +40,15 @@ const Dashboard: React.FC = () => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const v = parseVersion(instance.version);
|
const v = parseVersion(instance?.version ?? '0.0.0');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user_count: userCount,
|
user_count: userCount,
|
||||||
status_count: statusCount,
|
status_count: statusCount,
|
||||||
domain_count: domainCount,
|
domain_count: domainCount,
|
||||||
} = instance.stats;
|
} = instance?.stats ?? {};
|
||||||
|
|
||||||
const mau = instance.pleroma.stats.mau;
|
const mau = instance?.pleroma.stats.mau;
|
||||||
const retention = (userCount && mau) ? Math.round(mau / userCount * 100) : undefined;
|
const retention = (userCount && mau) ? Math.round(mau / userCount * 100) : undefined;
|
||||||
|
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { fireEvent, render, screen } from 'soapbox/jest/test-helpers';
|
import { fireEvent, render, screen } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import LoginForm from './login-form';
|
import LoginForm from './login-form';
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ describe('<LoginForm />', () => {
|
||||||
it('renders for Pleroma', () => {
|
it('renders for Pleroma', () => {
|
||||||
const mockFn = vi.fn();
|
const mockFn = vi.fn();
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ describe('<LoginForm />', () => {
|
||||||
it('renders for Mastodon', () => {
|
it('renders for Mastodon', () => {
|
||||||
const mockFn = vi.fn();
|
const mockFn = vi.fn();
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '3.0.0',
|
version: '3.0.0',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { render, screen } from 'soapbox/jest/test-helpers';
|
import { render, screen } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import LoginPage from './login-page';
|
import LoginPage from './login-page';
|
||||||
|
|
||||||
describe('<LoginPage />', () => {
|
describe('<LoginPage />', () => {
|
||||||
it('renders correctly on load', () => {
|
it('renders correctly on load', () => {
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,12 +3,12 @@ import React from 'react';
|
||||||
import { __stub } from 'soapbox/api';
|
import { __stub } from 'soapbox/api';
|
||||||
import { buildGroup } from 'soapbox/jest/factory';
|
import { buildGroup } from 'soapbox/jest/factory';
|
||||||
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import Search from './search';
|
import Search from './search';
|
||||||
|
|
||||||
const store = {
|
const store = {
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
|
|
||||||
import { buildAccount } from 'soapbox/jest/factory';
|
import { buildAccount } from 'soapbox/jest/factory';
|
||||||
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import Discover from './discover';
|
import Discover from './discover';
|
||||||
|
|
||||||
|
@ -32,9 +32,8 @@ const store: any = {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
instance: instanceSchema.parse({
|
instance: instanceV1Schema.parse({
|
||||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||||
software: 'TRUTHSOCIAL',
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
import { useAppSelector } from './useAppSelector';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useInstanceV1 } from 'soapbox/api/hooks/instance/useInstanceV1';
|
||||||
|
import { useInstanceV2 } from 'soapbox/api/hooks/instance/useInstanceV2';
|
||||||
|
import { instanceV2Schema, upgradeInstance } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
/** Get the Instance for the current backend. */
|
/** Get the Instance for the current backend. */
|
||||||
export const useInstance = () => {
|
export function useInstance() {
|
||||||
const instance = useAppSelector((state) => state.instance);
|
const v2 = useInstanceV2();
|
||||||
return { instance };
|
const v1 = useInstanceV1({ enabled: v2.isError });
|
||||||
};
|
|
||||||
|
const upgradedV1 = useMemo(() => {
|
||||||
|
if (v1.instance) {
|
||||||
|
return upgradeInstance(v1.instance);
|
||||||
|
}
|
||||||
|
}, [v1.instance]);
|
||||||
|
|
||||||
|
const instance = v2.instance ?? upgradedV1 ?? instanceV2Schema.parse({});
|
||||||
|
const props = v2.isError ? v1 : v2;
|
||||||
|
|
||||||
|
return { ...props, instance };
|
||||||
|
}
|
||||||
|
|
|
@ -15,10 +15,9 @@ import {
|
||||||
type GroupTag,
|
type GroupTag,
|
||||||
type Relationship,
|
type Relationship,
|
||||||
type Status,
|
type Status,
|
||||||
Instance,
|
|
||||||
instanceSchema,
|
|
||||||
} from 'soapbox/schemas';
|
} from 'soapbox/schemas';
|
||||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||||
|
import { InstanceV2, instanceV2Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
|
||||||
|
@ -71,8 +70,8 @@ function buildGroupMember(
|
||||||
}, props));
|
}, props));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInstance(props: PartialDeep<Instance> = {}) {
|
function buildInstance(props: PartialDeep<InstanceV2> = {}) {
|
||||||
return instanceSchema.parse(props);
|
return instanceV2Schema.parse(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRelationship(props: PartialDeep<Relationship> = {}): Relationship {
|
function buildRelationship(props: PartialDeep<Relationship> = {}): Relationship {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import alexJson from 'soapbox/__fixtures__/pleroma-account.json';
|
import alexJson from 'soapbox/__fixtures__/pleroma-account.json';
|
||||||
import { instanceSchema } from 'soapbox/schemas';
|
import { instanceV1Schema } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
import { buildAccount } from './factory';
|
import { buildAccount } from './factory';
|
||||||
|
|
||||||
/** Store with registrations open. */
|
/** Store with registrations open. */
|
||||||
const storeOpen = { instance: instanceSchema.parse({ registrations: true }) };
|
const storeOpen = { instance: instanceV1Schema.parse({ registrations: true }) };
|
||||||
|
|
||||||
/** Store with registrations closed. */
|
/** Store with registrations closed. */
|
||||||
const storeClosed = { instance: instanceSchema.parse({ registrations: false }) };
|
const storeClosed = { instance: instanceV1Schema.parse({ registrations: false }) };
|
||||||
|
|
||||||
/** Store with a logged-in user. */
|
/** Store with a logged-in user. */
|
||||||
const storeLoggedIn = {
|
const storeLoggedIn = {
|
||||||
|
|
|
@ -44,6 +44,7 @@ describe('instance reducer', () => {
|
||||||
expect(state.registrations).toBe(false);
|
expect(state.registrations).toBe(false);
|
||||||
|
|
||||||
// After importing the configs, registration will be open
|
// After importing the configs, registration will be open
|
||||||
|
// @ts-ignore don't know why the type is not working
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.registrations).toBe(true);
|
expect(result.registrations).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin';
|
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin';
|
||||||
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||||
import { type Instance, instanceSchema } from 'soapbox/schemas';
|
import { InstanceV1, instanceV1Schema, InstanceV2, instanceV2Schema, upgradeInstance } from 'soapbox/schemas/instance';
|
||||||
import KVStore from 'soapbox/storage/kv-store';
|
import KVStore from 'soapbox/storage/kv-store';
|
||||||
import { ConfigDB } from 'soapbox/utils/config-db';
|
import { ConfigDB } from 'soapbox/utils/config-db';
|
||||||
|
|
||||||
|
@ -13,22 +13,20 @@ import {
|
||||||
} from '../actions/instance';
|
} from '../actions/instance';
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
import type { APIEntity } from 'soapbox/types/entities';
|
|
||||||
|
|
||||||
const initialState: Instance = instanceSchema.parse({});
|
const initialState: InstanceV2 = instanceV2Schema.parse({});
|
||||||
|
|
||||||
const importInstance = (_state: Instance, instance: APIEntity): Instance => {
|
const importInstanceV1 = (_state: InstanceV2, instance: InstanceV1): InstanceV2 => {
|
||||||
return instanceSchema.parse(instance);
|
return upgradeInstance(instanceV1Schema.parse(instance));
|
||||||
};
|
};
|
||||||
|
|
||||||
const importInstanceV2 = (state: Instance, data: APIEntity): Instance => {
|
const importInstanceV2 = (_state: InstanceV2, data: InstanceV2): InstanceV2 => {
|
||||||
const instance = instanceSchema.parse(data);
|
return instanceV2Schema.parse(data);
|
||||||
return { ...instance, stats: state.stats };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const preloadImport = (state: Instance, action: Record<string, any>, path: string) => {
|
const preloadImport = (state: InstanceV2, action: Record<string, any>, path: string) => {
|
||||||
const instance = action.data[path];
|
const instance = action.data[path];
|
||||||
return instance ? importInstance(state, instance) : state;
|
return instance ? importInstanceV1(state, instance) : state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string) => {
|
const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string) => {
|
||||||
|
@ -38,7 +36,7 @@ const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string)
|
||||||
return v ? v.getIn(['tuple', 1]) : undefined;
|
return v ? v.getIn(['tuple', 1]) : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const importConfigs = (state: Instance, configs: ImmutableList<any>) => {
|
const importConfigs = (state: InstanceV2, configs: ImmutableList<any>) => {
|
||||||
// FIXME: This is pretty hacked together. Need to make a cleaner map.
|
// FIXME: This is pretty hacked together. Need to make a cleaner map.
|
||||||
const config = ConfigDB.find(configs, ':pleroma', ':instance');
|
const config = ConfigDB.find(configs, ':pleroma', ':instance');
|
||||||
const simplePolicy = ConfigDB.toSimplePolicy(configs);
|
const simplePolicy = ConfigDB.toSimplePolicy(configs);
|
||||||
|
@ -63,7 +61,7 @@ const importConfigs = (state: Instance, configs: ImmutableList<any>) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAuthFetch = (state: Instance) => {
|
const handleAuthFetch = (state: InstanceV1 | InstanceV2) => {
|
||||||
// Authenticated fetch is enabled, so make the instance appear censored
|
// Authenticated fetch is enabled, so make the instance appear censored
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -97,7 +95,7 @@ const persistInstanceV2 = ({ instance }: { instance: { domain: string } }, host:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInstanceFetchFail = (state: Instance, error: Record<string, any>) => {
|
const handleInstanceFetchFail = (state: InstanceV2, error: Record<string, any>) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
return handleAuthFetch(state);
|
return handleAuthFetch(state);
|
||||||
} else {
|
} else {
|
||||||
|
@ -111,7 +109,7 @@ export default function instance(state = initialState, action: AnyAction) {
|
||||||
return preloadImport(state, action, '/api/v1/instance');
|
return preloadImport(state, action, '/api/v1/instance');
|
||||||
case fetchInstance.fulfilled.type:
|
case fetchInstance.fulfilled.type:
|
||||||
persistInstance(action.payload);
|
persistInstance(action.payload);
|
||||||
return importInstance(state, action.payload.instance);
|
return importInstanceV1(state, action.payload.instance);
|
||||||
case fetchInstanceV2.fulfilled.type:
|
case fetchInstanceV2.fulfilled.type:
|
||||||
persistInstanceV2(action.payload);
|
persistInstanceV2(action.payload);
|
||||||
return importInstanceV2(state, action.payload.instance);
|
return importInstanceV2(state, action.payload.instance);
|
||||||
|
|
|
@ -12,7 +12,6 @@ export { groupSchema, type Group } from './group';
|
||||||
export { groupMemberSchema, type GroupMember } from './group-member';
|
export { groupMemberSchema, type GroupMember } from './group-member';
|
||||||
export { groupRelationshipSchema, type GroupRelationship } from './group-relationship';
|
export { groupRelationshipSchema, type GroupRelationship } from './group-relationship';
|
||||||
export { groupTagSchema, type GroupTag } from './group-tag';
|
export { groupTagSchema, type GroupTag } from './group-tag';
|
||||||
export { instanceSchema, type Instance } from './instance';
|
|
||||||
export { mentionSchema, type Mention } from './mention';
|
export { mentionSchema, type Mention } from './mention';
|
||||||
export { moderationLogEntrySchema, type ModerationLogEntry } from './moderation-log-entry';
|
export { moderationLogEntrySchema, type ModerationLogEntry } from './moderation-log-entry';
|
||||||
export { notificationSchema, type Notification } from './notification';
|
export { notificationSchema, type Notification } from './notification';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { instanceSchema } from './instance';
|
import { instanceV1Schema } from './instance';
|
||||||
|
|
||||||
describe('instanceSchema.parse()', () => {
|
describe('instanceV1Schema.parse()', () => {
|
||||||
it('normalizes an empty Map', () => {
|
it('normalizes an empty Map', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@ -67,7 +67,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
version: '0.0.0',
|
version: '0.0.0',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = instanceSchema.parse({});
|
const result = instanceV1Schema.parse({});
|
||||||
expect(result).toMatchObject(expected);
|
expect(result).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
expect(result).toMatchObject(expected);
|
expect(result).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
expect(result).toMatchObject(expected);
|
expect(result).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,13 +141,13 @@ describe('instanceSchema.parse()', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
expect(result).toMatchObject(expected);
|
expect(result).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes Fedibird instance', () => {
|
it('normalizes Fedibird instance', () => {
|
||||||
const instance = require('soapbox/__fixtures__/fedibird-instance.json');
|
const instance = require('soapbox/__fixtures__/fedibird-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
// Sets description_limit
|
// Sets description_limit
|
||||||
expect(result.pleroma.metadata.description_limit).toEqual(1500);
|
expect(result.pleroma.metadata.description_limit).toEqual(1500);
|
||||||
|
@ -158,7 +158,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
|
|
||||||
it('normalizes Mitra instance', () => {
|
it('normalizes Mitra instance', () => {
|
||||||
const instance = require('soapbox/__fixtures__/mitra-instance.json');
|
const instance = require('soapbox/__fixtures__/mitra-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
// Adds configuration and description_limit
|
// Adds configuration and description_limit
|
||||||
expect(result.configuration).toBeTruthy();
|
expect(result.configuration).toBeTruthy();
|
||||||
|
@ -167,7 +167,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
|
|
||||||
it('normalizes GoToSocial instance', () => {
|
it('normalizes GoToSocial instance', () => {
|
||||||
const instance = require('soapbox/__fixtures__/gotosocial-instance.json');
|
const instance = require('soapbox/__fixtures__/gotosocial-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
// Normalizes max_toot_chars
|
// Normalizes max_toot_chars
|
||||||
expect(result.configuration.statuses.max_characters).toEqual(5000);
|
expect(result.configuration.statuses.max_characters).toEqual(5000);
|
||||||
|
@ -180,7 +180,7 @@ describe('instanceSchema.parse()', () => {
|
||||||
|
|
||||||
it('normalizes Friendica instance', () => {
|
it('normalizes Friendica instance', () => {
|
||||||
const instance = require('soapbox/__fixtures__/friendica-instance.json');
|
const instance = require('soapbox/__fixtures__/friendica-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
// Normalizes max_toot_chars
|
// Normalizes max_toot_chars
|
||||||
expect(result.configuration.statuses.max_characters).toEqual(200000);
|
expect(result.configuration.statuses.max_characters).toEqual(200000);
|
||||||
|
@ -193,20 +193,20 @@ describe('instanceSchema.parse()', () => {
|
||||||
|
|
||||||
it('normalizes a Mastodon RC version', () => {
|
it('normalizes a Mastodon RC version', () => {
|
||||||
const instance = require('soapbox/__fixtures__/mastodon-instance-rc.json');
|
const instance = require('soapbox/__fixtures__/mastodon-instance-rc.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
expect(result.version).toEqual('3.5.0-rc1');
|
expect(result.version).toEqual('3.5.0-rc1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes Pixelfed instance', () => {
|
it('normalizes Pixelfed instance', () => {
|
||||||
const instance = require('soapbox/__fixtures__/pixelfed-instance.json');
|
const instance = require('soapbox/__fixtures__/pixelfed-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
expect(result.title).toBe('pixelfed');
|
expect(result.title).toBe('pixelfed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renames Akkoma to Pleroma', () => {
|
it('renames Akkoma to Pleroma', () => {
|
||||||
const instance = require('soapbox/__fixtures__/akkoma-instance.json');
|
const instance = require('soapbox/__fixtures__/akkoma-instance.json');
|
||||||
const result = instanceSchema.parse(instance);
|
const result = instanceV1Schema.parse(instance);
|
||||||
|
|
||||||
expect(result.version).toEqual('2.7.2 (compatible; Pleroma 2.4.50+akkoma)');
|
expect(result.version).toEqual('2.7.2 (compatible; Pleroma 2.4.50+akkoma)');
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ const versionSchema = z.string().catch('0.0.0').transform((version) => {
|
||||||
|
|
||||||
const configurationSchema = coerceObject({
|
const configurationSchema = coerceObject({
|
||||||
accounts: coerceObject({
|
accounts: coerceObject({
|
||||||
max_featured_tags: z.number().optional().catch(undefined),
|
max_featured_tags: z.number().catch(Infinity),
|
||||||
max_pinned_statuses: z.number().optional().catch(undefined),
|
max_pinned_statuses: z.number().catch(Infinity),
|
||||||
}),
|
}),
|
||||||
chats: coerceObject({
|
chats: coerceObject({
|
||||||
max_characters: z.number().catch(Infinity),
|
max_characters: z.number().catch(Infinity),
|
||||||
|
@ -48,18 +48,18 @@ const configurationSchema = coerceObject({
|
||||||
video_size_limit: z.number().optional().catch(undefined),
|
video_size_limit: z.number().optional().catch(undefined),
|
||||||
}),
|
}),
|
||||||
polls: coerceObject({
|
polls: coerceObject({
|
||||||
max_characters_per_option: z.number().optional().catch(undefined),
|
max_characters_per_option: z.number().catch(Infinity),
|
||||||
max_expiration: z.number().optional().catch(undefined),
|
max_expiration: z.number().catch(Infinity),
|
||||||
max_options: z.number().optional().catch(undefined),
|
max_options: z.number().catch(Infinity),
|
||||||
min_expiration: z.number().optional().catch(undefined),
|
min_expiration: z.number().catch(Infinity),
|
||||||
}),
|
}),
|
||||||
reactions: coerceObject({
|
reactions: coerceObject({
|
||||||
max_reactions: z.number().catch(0),
|
max_reactions: z.number().catch(0),
|
||||||
}),
|
}),
|
||||||
statuses: coerceObject({
|
statuses: coerceObject({
|
||||||
characters_reserved_per_url: z.number().optional().catch(undefined),
|
characters_reserved_per_url: z.number().optional().catch(undefined),
|
||||||
max_characters: z.number().optional().catch(undefined),
|
max_characters: z.number().catch(Infinity),
|
||||||
max_media_attachments: z.number().optional().catch(undefined),
|
max_media_attachments: z.number().catch(Infinity),
|
||||||
|
|
||||||
}),
|
}),
|
||||||
translation: coerceObject({
|
translation: coerceObject({
|
||||||
|
|
|
@ -6,7 +6,7 @@ import lt from 'semver/functions/lt';
|
||||||
import semverParse from 'semver/functions/parse';
|
import semverParse from 'semver/functions/parse';
|
||||||
|
|
||||||
import { custom } from 'soapbox/custom';
|
import { custom } from 'soapbox/custom';
|
||||||
import { type Instance } from 'soapbox/schemas';
|
import { InstanceV1, InstanceV2 } from 'soapbox/schemas/instance';
|
||||||
|
|
||||||
/** Import custom overrides, if exists */
|
/** Import custom overrides, if exists */
|
||||||
const overrides = custom('features');
|
const overrides = custom('features');
|
||||||
|
@ -102,7 +102,7 @@ export const REBASED = 'soapbox';
|
||||||
export const UNRELEASED = 'unreleased';
|
export const UNRELEASED = 'unreleased';
|
||||||
|
|
||||||
/** Parse features for the given instance */
|
/** Parse features for the given instance */
|
||||||
const getInstanceFeatures = (instance: Instance) => {
|
const getInstanceFeatures = (instance: InstanceV1 | InstanceV2) => {
|
||||||
const v = parseVersion(instance.version);
|
const v = parseVersion(instance.version);
|
||||||
const { features, federation } = instance.pleroma.metadata;
|
const { features, federation } = instance.pleroma.metadata;
|
||||||
|
|
||||||
|
@ -912,7 +912,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
v.software === FRIENDICA && gte(v.version, '2023.3.0'),
|
v.software === FRIENDICA && gte(v.version, '2023.3.0'),
|
||||||
v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.4.50'),
|
v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.4.50'),
|
||||||
features.includes('quote_posting'),
|
features.includes('quote_posting'),
|
||||||
instance.feature_quote === true,
|
'feature_quote' in instance && instance.feature_quote === true,
|
||||||
]),
|
]),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1087,7 +1087,7 @@ export type Features = ReturnType<typeof getInstanceFeatures>;
|
||||||
|
|
||||||
/** Detect backend features to conditionally render elements */
|
/** Detect backend features to conditionally render elements */
|
||||||
export const getFeatures = createSelector([
|
export const getFeatures = createSelector([
|
||||||
(instance: Instance) => instance,
|
(instance: InstanceV1 | InstanceV2) => instance,
|
||||||
], (instance): Features => {
|
], (instance): Features => {
|
||||||
const features = getInstanceFeatures(instance);
|
const features = getInstanceFeatures(instance);
|
||||||
return Object.assign(features, overrides) as Features;
|
return Object.assign(features, overrides) as Features;
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
/* eslint sort-keys: "error" */
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
import { parseVersion, PLEROMA, MITRA } from './features';
|
|
||||||
|
|
||||||
import type { Instance } from 'soapbox/schemas';
|
|
||||||
import type { RootState } from 'soapbox/store';
|
|
||||||
|
|
||||||
/** For solving bugs between API implementations. */
|
|
||||||
export const getQuirks = createSelector([
|
|
||||||
(instance: Instance) => parseVersion(instance.version),
|
|
||||||
], (v) => {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* The `next` and `prev` Link headers are backwards for blocks and mutes.
|
|
||||||
* @see GET /api/v1/blocks
|
|
||||||
* @see GET /api/v1/mutes
|
|
||||||
*/
|
|
||||||
invertedPagination: v.software === PLEROMA,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apps are not supported by the API, and should not be created during login or registration.
|
|
||||||
* @see POST /api/v1/apps
|
|
||||||
* @see POST /oauth/token
|
|
||||||
*/
|
|
||||||
noApps: v.software === MITRA,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Shortcut for inverted pagination quirk. */
|
|
||||||
export const getNextLinkName = (getState: () => RootState) =>
|
|
||||||
getQuirks(getState().instance).invertedPagination ? 'prev' : 'next';
|
|
|
@ -1,15 +1,14 @@
|
||||||
|
|
||||||
import { PLEROMA, parseVersion } from './features';
|
import { PLEROMA, parseVersion } from './features';
|
||||||
|
|
||||||
import type { Instance } from 'soapbox/schemas';
|
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the OAuth scopes to use for login & signup.
|
* Get the OAuth scopes to use for login & signup.
|
||||||
* Mastodon will refuse scopes it doesn't know, so care is needed.
|
* Mastodon will refuse scopes it doesn't know, so care is needed.
|
||||||
*/
|
*/
|
||||||
const getInstanceScopes = (instance: Instance) => {
|
const getInstanceScopes = (version: string) => {
|
||||||
const v = parseVersion(instance.version);
|
const v = parseVersion(version);
|
||||||
|
|
||||||
switch (v.software) {
|
switch (v.software) {
|
||||||
case PLEROMA:
|
case PLEROMA:
|
||||||
|
@ -20,7 +19,7 @@ const getInstanceScopes = (instance: Instance) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Convenience function to get scopes from instance in store. */
|
/** Convenience function to get scopes from instance in store. */
|
||||||
const getScopes = (state: RootState) => getInstanceScopes(state.instance);
|
const getScopes = (state: RootState) => getInstanceScopes(state.instance.version);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getInstanceScopes,
|
getInstanceScopes,
|
||||||
|
|
Loading…
Reference in New Issue