Differentiate instance V1 and V2
Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1635
This commit is contained in:
parent
d97602aa74
commit
1e9b209f06
4 changed files with 30 additions and 136 deletions
|
@ -2,7 +2,6 @@ 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, getMeUrl } from 'soapbox/utils/auth';
|
||||
import { MASTODON, parseVersion, PLEROMA, REBASED } from 'soapbox/utils/features';
|
||||
|
@ -20,15 +19,6 @@ export const getHost = (state: RootState) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const rememberInstance = createAsyncThunk(
|
||||
'instance/remember',
|
||||
async(host: string) => {
|
||||
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')) ||
|
||||
|
@ -41,14 +31,19 @@ const needsNodeinfo = (instance: Record<string, any>): boolean => {
|
|||
return v.software === PLEROMA && !get(instance, ['pleroma', 'metadata']);
|
||||
};
|
||||
|
||||
export const fetchInstance = createAsyncThunk<{ instance: Record<string, any>; host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||
interface InstanceData {
|
||||
instance: Record<string, any>;
|
||||
host: string | null | undefined;
|
||||
}
|
||||
|
||||
export const fetchInstance = createAsyncThunk<InstanceData, InstanceData['host'], { state: RootState }>(
|
||||
'instance/fetch',
|
||||
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 };
|
||||
dispatch(fetchInstanceV2(host));
|
||||
}
|
||||
|
||||
if (needsNodeinfo(instance)) {
|
||||
|
@ -61,8 +56,8 @@ export const fetchInstance = createAsyncThunk<{ instance: Record<string, any>; h
|
|||
},
|
||||
);
|
||||
|
||||
export const fetchInstanceV2 = createAsyncThunk<{ instance: Record<string, any>; host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||
'instance/fetch',
|
||||
export const fetchInstanceV2 = createAsyncThunk<InstanceData, InstanceData['host'], { state: RootState }>(
|
||||
'instanceV2/fetch',
|
||||
async(host, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const { data: instance } = await api(getState).get('/api/v2/instance');
|
||||
|
@ -73,21 +68,6 @@ export const fetchInstanceV2 = createAsyncThunk<{ instance: Record<string, any>;
|
|||
},
|
||||
);
|
||||
|
||||
/** Tries to remember the instance from browser storage before fetching it */
|
||||
export const loadInstance = createAsyncThunk<void, void, { state: RootState }>(
|
||||
'instance/load',
|
||||
async(_arg, { dispatch, getState }) => {
|
||||
const host = getHost(getState());
|
||||
const rememberedInstance = await dispatch(rememberInstance(host || ''));
|
||||
|
||||
if (rememberedInstance.payload && supportsInstanceV2((rememberedInstance.payload as any).instance)) {
|
||||
await dispatch(fetchInstanceV2(host));
|
||||
} else {
|
||||
await dispatch(fetchInstance(host));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchNodeinfo = createAsyncThunk<void, void, { state: RootState }>(
|
||||
'nodeinfo/fetch',
|
||||
async(_arg, { getState }) => await api(getState).get('/nodeinfo/2.1.json'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { loadInstance } from 'soapbox/actions/instance';
|
||||
import { fetchInstance } from 'soapbox/actions/instance';
|
||||
import { fetchMe } from 'soapbox/actions/me';
|
||||
import { loadSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import LoadingScreen from 'soapbox/components/loading-screen';
|
||||
|
@ -20,7 +20,7 @@ const loadInitial = () => {
|
|||
// Await for authenticated fetch
|
||||
await dispatch(fetchMe());
|
||||
// Await for feature detection
|
||||
await dispatch(loadInstance());
|
||||
await dispatch(fetchInstance());
|
||||
// Await for configuration
|
||||
await dispatch(loadSoapboxConfig());
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ADMIN_CONFIG_UPDATE_REQUEST } from 'soapbox/actions/admin';
|
||||
import { rememberInstance } from 'soapbox/actions/instance';
|
||||
|
||||
import reducer from './instance';
|
||||
|
||||
|
@ -31,100 +30,6 @@ describe('instance reducer', () => {
|
|||
expect(result).toMatchObject(expected);
|
||||
});
|
||||
|
||||
describe('rememberInstance.fulfilled', () => {
|
||||
it('normalizes Pleroma instance with Mastodon configuration format', async () => {
|
||||
const payload = await import('soapbox/__fixtures__/pleroma-instance.json');
|
||||
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload,
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
||||
const expected = {
|
||||
configuration: {
|
||||
statuses: {
|
||||
max_characters: 5000,
|
||||
max_media_attachments: Infinity,
|
||||
},
|
||||
polls: {
|
||||
max_options: 20,
|
||||
max_characters_per_option: 200,
|
||||
min_expiration: 0,
|
||||
max_expiration: 31536000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(result).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('normalizes Mastodon instance with retained configuration', async () => {
|
||||
const payload = await import('soapbox/__fixtures__/mastodon-instance.json');
|
||||
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload,
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
||||
const expected = {
|
||||
configuration: {
|
||||
statuses: {
|
||||
max_characters: 500,
|
||||
max_media_attachments: 4,
|
||||
characters_reserved_per_url: 23,
|
||||
},
|
||||
media_attachments: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(result).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('normalizes Mastodon 3.0.0 instance with default configuration', async () => {
|
||||
const payload = await import('soapbox/__fixtures__/mastodon-3.0.0-instance.json');
|
||||
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload,
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
||||
const expected = {
|
||||
configuration: {
|
||||
statuses: {
|
||||
max_characters: 500,
|
||||
max_media_attachments: 4,
|
||||
},
|
||||
polls: {
|
||||
max_options: 4,
|
||||
max_characters_per_option: 25,
|
||||
min_expiration: 300,
|
||||
max_expiration: 2629746,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(result).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADMIN_CONFIG_UPDATE_REQUEST', async () => {
|
||||
const { configs } = await import('soapbox/__fixtures__/pleroma-admin-config.json');
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import KVStore from 'soapbox/storage/kv-store';
|
|||
import { ConfigDB } from 'soapbox/utils/config-db';
|
||||
|
||||
import {
|
||||
rememberInstance,
|
||||
fetchInstance,
|
||||
fetchInstanceV2,
|
||||
} from '../actions/instance';
|
||||
|
@ -18,11 +17,16 @@ import type { APIEntity } from 'soapbox/types/entities';
|
|||
|
||||
const initialState: Instance = instanceSchema.parse({});
|
||||
|
||||
const importInstance = (_state: typeof initialState, instance: APIEntity) => {
|
||||
const importInstance = (_state: Instance, instance: APIEntity): Instance => {
|
||||
return instanceSchema.parse(instance);
|
||||
};
|
||||
|
||||
const preloadImport = (state: typeof initialState, action: Record<string, any>, path: string) => {
|
||||
const importInstanceV2 = (state: Instance, data: APIEntity): Instance => {
|
||||
const instance = instanceSchema.parse(data);
|
||||
return { ...instance, stats: state.stats };
|
||||
};
|
||||
|
||||
const preloadImport = (state: Instance, action: Record<string, any>, path: string) => {
|
||||
const instance = action.data[path];
|
||||
return instance ? importInstance(state, instance) : state;
|
||||
};
|
||||
|
@ -34,7 +38,7 @@ const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string)
|
|||
return v ? v.getIn(['tuple', 1]) : undefined;
|
||||
};
|
||||
|
||||
const importConfigs = (state: typeof initialState, configs: ImmutableList<any>) => {
|
||||
const importConfigs = (state: Instance, configs: ImmutableList<any>) => {
|
||||
// FIXME: This is pretty hacked together. Need to make a cleaner map.
|
||||
const config = ConfigDB.find(configs, ':pleroma', ':instance');
|
||||
const simplePolicy = ConfigDB.toSimplePolicy(configs);
|
||||
|
@ -59,7 +63,7 @@ const importConfigs = (state: typeof initialState, configs: ImmutableList<any>)
|
|||
});
|
||||
};
|
||||
|
||||
const handleAuthFetch = (state: typeof initialState) => {
|
||||
const handleAuthFetch = (state: Instance) => {
|
||||
// Authenticated fetch is enabled, so make the instance appear censored
|
||||
return {
|
||||
...state,
|
||||
|
@ -86,7 +90,13 @@ const persistInstance = (instance: { uri: string }, host: string | null = getHos
|
|||
}
|
||||
};
|
||||
|
||||
const handleInstanceFetchFail = (state: typeof initialState, error: Record<string, any>) => {
|
||||
const persistInstanceV2 = (instance: { uri: string }, host: string | null = getHost(instance)) => {
|
||||
if (host) {
|
||||
KVStore.setItem(`instanceV2:${host}`, instance).catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstanceFetchFail = (state: Instance, error: Record<string, any>) => {
|
||||
if (error.response?.status === 401) {
|
||||
return handleAuthFetch(state);
|
||||
} else {
|
||||
|
@ -98,14 +108,13 @@ export default function instance(state = initialState, action: AnyAction) {
|
|||
switch (action.type) {
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action, '/api/v1/instance');
|
||||
case rememberInstance.fulfilled.type:
|
||||
return importInstance(state, action.payload.instance);
|
||||
case fetchInstance.fulfilled.type:
|
||||
case fetchInstanceV2.fulfilled.type:
|
||||
persistInstance(action.payload);
|
||||
return importInstance(state, action.payload.instance);
|
||||
case fetchInstanceV2.fulfilled.type:
|
||||
persistInstanceV2(action.payload);
|
||||
return importInstanceV2(state, action.payload.instance);
|
||||
case fetchInstance.rejected.type:
|
||||
case fetchInstanceV2.rejected.type:
|
||||
return handleInstanceFetchFail(state, action.error);
|
||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
|
|
Loading…
Reference in a new issue