Files
upstream-rocketchat/apps/meteor/client/providers/SettingsProvider.tsx
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

160 lines
5.3 KiB
TypeScript

import type { ISetting } from '@rocket.chat/core-typings';
import { isSettingCode } from '@rocket.chat/core-typings';
import { createPredicateFromFilter } from '@rocket.chat/mongo-adapter';
import { isTruthy } from '@rocket.chat/tools';
import type { SettingsContextQuery, SettingsContextValue } from '@rocket.chat/ui-contexts';
import { SettingsContext, useAtLeastOnePermission, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import type { ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
import { PublicSettingsCachedStore, PrivateSettingsCachedStore } from '../cachedStores';
import { useShowSettingAlerts } from '../hooks/useShowSettingAlerts';
import { PrivateCachedStore } from '../lib/cachedStores/CachedStore';
import { applyQueryOptions } from '../lib/cachedStores/applyQueryOptions';
import { getCodeSettingError } from '../lib/utils/getCodeSettingError';
const settingsManagementPermissions = ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings'];
export type SettingsProviderProps = {
children?: ReactNode;
};
const SettingsProvider = ({ children }: SettingsProviderProps) => {
const canManageSettings = useAtLeastOnePermission(settingsManagementPermissions);
const cachedCollection = canManageSettings ? PrivateSettingsCachedStore : PublicSettingsCachedStore;
const isLoading = !cachedCollection.useReady();
if (isLoading) {
throw (async () => {
if (cachedCollection instanceof PrivateCachedStore) {
cachedCollection.listen();
}
await cachedCollection.init();
})();
}
const querySetting = useMemo(
() =>
(_id: ISetting['_id']): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined] => {
let snapshot = cachedCollection.store.getState().get(_id);
const subscribe = (onStoreChange: () => void) =>
cachedCollection.store.subscribe(() => {
const newSnapshot = cachedCollection.store.getState().get(_id);
if (newSnapshot === snapshot) return;
snapshot = newSnapshot;
onStoreChange();
});
const getSnapshot = () => snapshot;
return [subscribe, getSnapshot];
},
[cachedCollection.store],
);
const querySettings = useMemo(() => {
return (query: SettingsContextQuery): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting[]] => {
const effectiveQuery = {
...('_id' in query && Array.isArray(query._id) && { _id: { $in: query._id } }),
...('_id' in query && !Array.isArray(query._id) && { _id: query._id }),
...('group' in query && { group: query.group }),
...('section' in query &&
(query.section
? { section: query.section }
: {
$or: [{ section: { $exists: false } }, { section: undefined }],
})),
};
const options = {
sort: {
section: 1,
sorter: 1,
i18nLabel: 1,
},
...('skip' in query && typeof query.skip === 'number' && { skip: query.skip }),
...('limit' in query && typeof query.limit === 'number' && { limit: query.limit }),
} as const;
const predicate = createPredicateFromFilter<ISetting>(effectiveQuery);
let snapshot = applyQueryOptions(cachedCollection.store.getState().filter(predicate), options);
const subscribe = (onStoreChange: () => void) =>
cachedCollection.store.subscribe(() => {
const newSnapshot = applyQueryOptions(cachedCollection.store.getState().filter(predicate), options);
if (newSnapshot === snapshot) return;
snapshot = newSnapshot;
onStoreChange();
});
const getSnapshot = () => snapshot;
return [subscribe, getSnapshot];
};
}, [cachedCollection.store]);
const queryClient = useQueryClient();
const showAlerts = useShowSettingAlerts();
const saveSettings = useEndpoint('POST', '/v1/settings');
const dispatch = useCallback(
async (changes: Partial<ISetting>[], onSaved?: () => void) => {
const changedSettingIds = changes.map((s) => s._id).filter(isTruthy);
const alerts = cachedCollection.store
.getState()
.filter((setting) => Boolean(setting.alert && changedSettingIds.includes(setting._id)));
// FIXME: This is a temporary solution to invalidate queries when settings change
changes.forEach((val) => {
if (val._id === 'Enterprise_License') {
queryClient.invalidateQueries({ queryKey: ['licenses'] });
}
});
const hasInvalidCodeSetting = changes.some((change) => {
if (!change._id || change.value === undefined) {
return false;
}
const setting = cachedCollection.store.getState().get(change._id);
return setting !== undefined && isSettingCode(setting) && getCodeSettingError(setting.code, change.value) !== undefined;
});
if (hasInvalidCodeSetting) {
return;
}
if (alerts.length) {
const accepted = await showAlerts(alerts);
if (!accepted) {
return;
}
}
await saveSettings({ settings: changes as Pick<ISetting, '_id' | 'value'>[] });
onSaved?.();
},
[queryClient, saveSettings, showAlerts, cachedCollection.store],
);
const contextValue = useMemo<SettingsContextValue>(
() => ({
hasPrivateAccess: canManageSettings,
querySetting,
querySettings,
dispatch,
}),
[canManageSettings, querySetting, querySettings, dispatch],
);
return <SettingsContext.Provider value={contextValue}>{children}</SettingsContext.Provider>;
};
export default SettingsProvider;