The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/client/admin/apps/AppProvider.tsx

186 lines
4.8 KiB

import React, {
createContext,
useEffect,
useRef,
useState,
FunctionComponent,
} from 'react';
import { Apps } from '../../../app/apps/client/orchestrator';
import { AppEvents } from '../../../app/apps/client/communication';
import { handleAPIError } from './helpers';
type App = {
id: string;
name: string;
status: unknown;
installed: boolean;
marketplace: unknown;
version: unknown;
marketplaceVersion: unknown;
bundledIn: unknown;
};
export type AppDataContextValue = {
data: App[];
dataCache: any;
finishedLoading: boolean;
}
export const AppDataContext = createContext<AppDataContextValue>({
data: [],
dataCache: [],
finishedLoading: false,
});
type ListenersMapping = {
readonly [P in keyof typeof AppEvents]?: (...args: any[]) => void;
};
const registerListeners = (listeners: ListenersMapping): (() => void) => {
const entries = Object.entries(listeners) as [keyof typeof AppEvents, () => void][];
for (const [event, callback] of entries) {
Apps.getWsListener()?.registerListener(AppEvents[event], callback);
}
return (): void => {
for (const [event, callback] of entries) {
Apps.getWsListener()?.unregisterListener(AppEvents[event], callback);
}
};
};
const AppProvider: FunctionComponent = ({ children }) => {
const [data, setData] = useState<App[]>(() => []);
const [finishedLoading, setFinishedLoading] = useState<boolean>(() => false);
const [dataCache, setDataCache] = useState<any>(() => []);
const ref = useRef(data);
ref.current = data;
const invalidateData = (): void => {
setDataCache(() => []);
};
const getDataCopy = (): typeof ref.current => ref.current.slice(0);
useEffect(() => {
const updateData = (data: App[]): void => {
setData(data.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)));
invalidateData();
};
const handleAppAddedOrUpdated = async (appId: string): Promise<void> => {
try {
const { status, version } = await Apps.getApp(appId);
const app = await Apps.getAppFromMarketplace(appId, version);
const updatedData = getDataCopy();
const index = updatedData.findIndex(({ id }: {id: string}) => id === appId);
updatedData[index] = {
...app,
installed: true,
status,
version,
marketplaceVersion: app.version,
};
setData(updatedData);
invalidateData();
} catch (error) {
handleAPIError(error);
}
};
const listeners = {
APP_ADDED: handleAppAddedOrUpdated,
APP_UPDATED: handleAppAddedOrUpdated,
APP_REMOVED: (appId: string): void => {
const updatedData = getDataCopy();
const index = updatedData.findIndex(({ id }: {id: string}) => id === appId);
if (!updatedData[index]) {
return;
}
if (updatedData[index].marketplace !== false) {
updatedData.splice(index, 1);
} else {
delete updatedData[index].installed;
delete updatedData[index].status;
updatedData[index].version = updatedData[index].marketplaceVersion;
}
setData(updatedData);
invalidateData();
},
APP_STATUS_CHANGE: ({ appId, status }: {appId: string; status: unknown}): void => {
const updatedData = getDataCopy();
const app = updatedData.find(({ id }: {id: string}) => id === appId);
if (!app) {
return;
}
app.status = status;
setData(updatedData);
invalidateData();
},
APP_SETTING_UPDATED: (): void => {
invalidateData();
},
};
const unregisterListeners = registerListeners(listeners);
(async (): Promise<void> => {
try {
const installedApps = await Apps.getApps().then((result) => {
let apps: App[] = [];
if (result.length) {
apps = result.map((current: App) => ({ ...current, installed: true, marketplace: false }));
updateData(apps);
}
return apps;
}) as App[];
const marketplaceApps = await Apps.getAppsFromMarketplace() as App[];
const appsData = marketplaceApps.length ? marketplaceApps.map((app) => {
const appIndex = installedApps.findIndex(({ id }) => id === app.id);
if (!installedApps[appIndex]) {
return {
...app,
status: undefined,
marketplaceVersion: app.version,
bundledIn: app.bundledIn,
};
}
const installedApp = installedApps.splice(appIndex, 1).pop();
return {
...app,
installed: true,
status: installedApp?.status,
version: installedApp?.version,
bundledIn: app.bundledIn,
marketplaceVersion: app.version,
};
}) : [];
if (installedApps.length) {
appsData.push(...installedApps);
}
updateData(appsData);
setFinishedLoading(true);
} catch (e) {
handleAPIError(e);
unregisterListeners();
}
})();
return unregisterListeners;
}, [setData, setFinishedLoading]);
return <AppDataContext.Provider children={children} value={{ data, dataCache, finishedLoading }} />;
};
export default AppProvider;