From 16da9030ac0754638d105448810ac3d78ddb2092 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 23 Feb 2022 11:24:36 -0500 Subject: [PATCH] normalizeInstance: break out instance normalization into its own module --- .../normalizers/__tests__/instance-test.js | 101 ++++++++++++++++++ app/soapbox/normalizers/instance.js | 65 +++++++++++ app/soapbox/reducers/instance.js | 66 +----------- 3 files changed, 169 insertions(+), 63 deletions(-) create mode 100644 app/soapbox/normalizers/__tests__/instance-test.js create mode 100644 app/soapbox/normalizers/instance.js diff --git a/app/soapbox/normalizers/__tests__/instance-test.js b/app/soapbox/normalizers/__tests__/instance-test.js new file mode 100644 index 000000000..28eea3191 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/instance-test.js @@ -0,0 +1,101 @@ +import { Map as ImmutableMap, fromJS } from 'immutable'; + +import { normalizeInstance } from '../instance'; + +describe('normalizeInstance()', () => { + it('normalizes an empty Map', () => { + const expected = ImmutableMap({ + description_limit: 1500, + configuration: ImmutableMap({ + statuses: ImmutableMap({ + max_characters: 500, + max_media_attachments: 4, + }), + polls: ImmutableMap({ + max_options: 4, + max_characters_per_option: 25, + min_expiration: 300, + max_expiration: 2629746, + }), + }), + version: '0.0.0', + }); + + const result = normalizeInstance(ImmutableMap()); + expect(result).toEqual(expected); + }); + + it('normalizes Pleroma instance with Mastodon configuration format', () => { + const instance = fromJS(require('soapbox/__fixtures__/pleroma-instance.json')); + + 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, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); + + it('normalizes Mastodon instance with retained configuration', () => { + const instance = fromJS(require('soapbox/__fixtures__/mastodon-instance.json')); + + 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, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); + + it('normalizes Mastodon 3.0.0 instance with default configuration', () => { + const instance = fromJS(require('soapbox/__fixtures__/mastodon-3.0.0-instance.json')); + + 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, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); +}); diff --git a/app/soapbox/normalizers/instance.js b/app/soapbox/normalizers/instance.js new file mode 100644 index 000000000..d95990670 --- /dev/null +++ b/app/soapbox/normalizers/instance.js @@ -0,0 +1,65 @@ +import { Map as ImmutableMap } from 'immutable'; + +import { parseVersion, PLEROMA } from 'soapbox/utils/features'; +import { isNumber } from 'soapbox/utils/numbers'; + +// Use Mastodon defaults +const baseInstance = ImmutableMap({ + description_limit: 1500, + configuration: ImmutableMap({ + statuses: ImmutableMap({ + max_characters: 500, + max_media_attachments: 4, + }), + polls: ImmutableMap({ + max_options: 4, + max_characters_per_option: 25, + min_expiration: 300, + max_expiration: 2629746, + }), + }), + version: '0.0.0', +}); + +// Build Mastodon configuration from Pleroma instance +const pleromaToMastodonConfig = instance => { + return ImmutableMap({ + statuses: ImmutableMap({ + max_characters: instance.get('max_toot_chars'), + }), + polls: ImmutableMap({ + max_options: instance.getIn(['poll_limits', 'max_options']), + max_characters_per_option: instance.getIn(['poll_limits', 'max_option_chars']), + min_expiration: instance.getIn(['poll_limits', 'min_expiration']), + max_expiration: instance.getIn(['poll_limits', 'max_expiration']), + }), + }); +}; + +// Use new value only if old value is undefined +const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; + +// Get the software's default attachment limit +const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; + +// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format +export const normalizeInstance = instance => { + const { software } = parseVersion(instance.get('version')); + const mastodonConfig = pleromaToMastodonConfig(instance); + + return instance.withMutations(instance => { + // Merge configuration + instance.update('configuration', ImmutableMap(), configuration => ( + configuration.mergeDeepWith(mergeDefined, mastodonConfig) + )); + + // If max attachments isn't set, check the backend software + instance.updateIn(['configuration', 'statuses', 'max_media_attachments'], value => { + return isNumber(value) ? value : getAttachmentLimit(software); + }); + + // Merge defaults & cleanup + instance.mergeDeepWith(mergeDefined, baseInstance); + instance.deleteAll(['max_toot_chars', 'poll_limits']); + }); +}; diff --git a/app/soapbox/reducers/instance.js b/app/soapbox/reducers/instance.js index 8a79a2869..ac28e88ee 100644 --- a/app/soapbox/reducers/instance.js +++ b/app/soapbox/reducers/instance.js @@ -2,10 +2,9 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin'; import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload'; +import { normalizeInstance } from 'soapbox/normalizers/instance'; import KVStore from 'soapbox/storage/kv_store'; import { ConfigDB } from 'soapbox/utils/config_db'; -import { parseVersion, PLEROMA } from 'soapbox/utils/features'; -import { isNumber } from 'soapbox/utils/numbers'; import { INSTANCE_REMEMBER_SUCCESS, @@ -14,6 +13,8 @@ import { NODEINFO_FETCH_SUCCESS, } from '../actions/instance'; +const initialState = normalizeInstance(ImmutableMap()); + const nodeinfoToInstance = nodeinfo => { // Match Pleroma's develop branch return ImmutableMap({ @@ -30,67 +31,6 @@ const nodeinfoToInstance = nodeinfo => { }); }; -// Use Mastodon defaults -const initialState = ImmutableMap({ - description_limit: 1500, - configuration: ImmutableMap({ - statuses: ImmutableMap({ - max_characters: 500, - max_media_attachments: 4, - }), - polls: ImmutableMap({ - max_options: 4, - max_characters_per_option: 25, - min_expiration: 300, - max_expiration: 2629746, - }), - }), - version: '0.0.0', -}); - -// Build Mastodon configuration from Pleroma instance -const pleromaToMastodonConfig = instance => { - return ImmutableMap({ - statuses: ImmutableMap({ - max_characters: instance.get('max_toot_chars'), - }), - polls: ImmutableMap({ - max_options: instance.getIn(['poll_limits', 'max_options']), - max_characters_per_option: instance.getIn(['poll_limits', 'max_option_chars']), - min_expiration: instance.getIn(['poll_limits', 'min_expiration']), - max_expiration: instance.getIn(['poll_limits', 'max_expiration']), - }), - }); -}; - -// Use new value only if old value is undefined -const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; - -// Get the software's default attachment limit -const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; - -// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format -const normalizeInstance = instance => { - const { software } = parseVersion(instance.get('version')); - const mastodonConfig = pleromaToMastodonConfig(instance); - - return instance.withMutations(instance => { - // Merge configuration - instance.update('configuration', ImmutableMap(), configuration => ( - configuration.mergeDeepWith(mergeDefined, mastodonConfig) - )); - - // If max attachments isn't set, check the backend software - instance.updateIn(['configuration', 'statuses', 'max_media_attachments'], value => { - return isNumber(value) ? value : getAttachmentLimit(software); - }); - - // Merge defaults & cleanup - instance.mergeDeepWith(mergeDefined, initialState); - instance.deleteAll(['max_toot_chars', 'poll_limits']); - }); -}; - const importInstance = (state, instance) => { return normalizeInstance(instance); };