Files
mattermost-mobile/app/managers/apps_manager.ts

219 lines
8.4 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BehaviorSubject, combineLatest, of as of$} from 'rxjs';
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
import DatabaseManager from '@database/manager';
import {getChannelById} from '@queries/servers/channel';
import {getConfig, getCurrentChannelId, getCurrentTeamId, getCurrentUserId, observeConfigBooleanValue} from '@queries/servers/system';
import {validateBindings} from '@utils/apps';
import {logDebug} from '@utils/log';
import NetworkManager from './network_manager';
const emptyBindings: AppBinding[] = [];
const normalizeBindings = (bindings: AppBinding[]) => bindings.reduce<AppBinding[]>((acc, v) => (v.bindings ? acc.concat(v.bindings) : acc), []);
class AppsManager {
private enabled: {[serverUrl: string]: BehaviorSubject<boolean>} = {};
private bindings: {[serverUrl: string]: BehaviorSubject<AppBinding[]>} = {};
private threadBindings: {[serverUrl: string]: BehaviorSubject<{channelId: string; bindings: AppBinding[]}>} = {};
private commandForms: {[serverUrl: string]: {[location: string]: AppForm}} = {};
private threadCommandForms: {[serverUrl: string]: {[location: string]: AppForm}} = {};
private getEnabledSubject = (serverUrl: string) => {
if (!this.enabled[serverUrl]) {
this.enabled[serverUrl] = new BehaviorSubject(true);
}
return this.enabled[serverUrl];
};
private getBindingsSubject = (serverUrl: string) => {
if (!this.bindings[serverUrl]) {
this.bindings[serverUrl] = new BehaviorSubject([]);
}
return this.bindings[serverUrl];
};
private getThreadsBindingsSubject = (serverUrl: string) => {
if (!this.threadBindings[serverUrl]) {
this.threadBindings[serverUrl] = new BehaviorSubject({channelId: '', bindings: emptyBindings});
}
return this.threadBindings[serverUrl];
};
private handleError = (serverUrl: string) => {
const enabled = this.getEnabledSubject(serverUrl);
if (enabled.value) {
enabled.next(false);
}
this.getBindingsSubject(serverUrl).next(emptyBindings);
this.getThreadsBindingsSubject(serverUrl).next({channelId: '', bindings: emptyBindings});
this.commandForms[serverUrl] = {};
this.threadCommandForms[serverUrl] = {};
};
removeServer = (serverUrl: string) => {
delete (this.enabled[serverUrl]);
delete (this.bindings[serverUrl]);
delete (this.threadBindings[serverUrl]);
delete (this.commandForms[serverUrl]);
delete (this.threadCommandForms[serverUrl]);
};
clearServer = (serverUrl: string) => {
this.clearBindings(serverUrl);
this.clearBindings(serverUrl, true);
this.commandForms[serverUrl] = {};
this.threadCommandForms[serverUrl] = {};
};
isAppsEnabled = async (serverUrl: string) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const config = await getConfig(database);
return this.getEnabledSubject(serverUrl).value && config?.FeatureFlagAppsEnabled === 'true';
} catch {
return false;
}
};
observeIsAppsEnabled = (serverUrl: string) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const enabled = this.getEnabledSubject(serverUrl).asObservable();
const config = observeConfigBooleanValue(database, 'FeatureFlagAppsEnabled');
return combineLatest([enabled, config]).pipe(
switchMap(([e, cfg]) => of$(e && cfg)),
distinctUntilChanged(),
);
} catch {
return of$(false);
}
};
fetchBindings = async (serverUrl: string, channelId: string, forThread = false) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const userId = await getCurrentUserId(database);
const channel = await getChannelById(database, channelId);
let teamId = channel?.teamId;
if (!teamId) {
teamId = await getCurrentTeamId(database);
}
const client = NetworkManager.getClient(serverUrl);
const fetchedBindings = await client.getAppsBindings(userId, channelId, teamId);
const validatedBindings = validateBindings(fetchedBindings);
const bindingsToStore = validatedBindings.length ? validatedBindings : emptyBindings;
const enabled = this.getEnabledSubject(serverUrl);
if (!enabled.value) {
enabled.next(true);
}
if (forThread) {
this.getThreadsBindingsSubject(serverUrl).next({channelId, bindings: bindingsToStore});
this.threadCommandForms[serverUrl] = {};
} else {
this.getBindingsSubject(serverUrl).next(bindingsToStore);
this.commandForms[serverUrl] = {};
}
} catch (error) {
logDebug('Error fetching apps', error);
this.handleError(serverUrl);
}
};
refreshAppBindings = async (serverUrl: string) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const appsEnabled = (await getConfig(database))?.FeatureFlagAppsEnabled === 'true';
if (!appsEnabled) {
this.getEnabledSubject(serverUrl).next(false);
this.clearServer(serverUrl);
}
const channelId = await getCurrentChannelId(database);
// We await here, since errors on this call may clear the thread bindings
await this.fetchBindings(serverUrl, channelId);
const threadChannelId = this.getThreadsBindingsSubject(serverUrl).value.channelId;
if (threadChannelId) {
await this.fetchBindings(serverUrl, threadChannelId, true);
}
} catch (error) {
logDebug('Error refreshing apps', error);
this.handleError(serverUrl);
}
};
copyMainBindingsToThread = async (serverUrl: string, channelId: string) => {
this.getThreadsBindingsSubject(serverUrl).next({channelId, bindings: this.getBindingsSubject(serverUrl).value});
};
clearBindings = async (serverUrl: string, forThread = false) => {
if (forThread) {
this.getThreadsBindingsSubject(serverUrl).next({channelId: '', bindings: emptyBindings});
} else {
this.getBindingsSubject(serverUrl).next(emptyBindings);
}
};
observeBindings = (serverUrl: string, location?: string, forThread = false) => {
const isEnabled = this.observeIsAppsEnabled(serverUrl);
const bindings = forThread ?
this.getThreadsBindingsSubject(serverUrl).asObservable().pipe(switchMap(({bindings: bb}) => of$(bb))) :
this.getBindingsSubject(serverUrl).asObservable();
return combineLatest([isEnabled, bindings]).pipe(
switchMap(([e, bb]) => of$(e ? bb : emptyBindings)),
switchMap((bb) => {
let result = location ? bb.filter((b) => b.location === location) : bb;
result = normalizeBindings(result);
return of$(result.length ? result : emptyBindings);
}),
);
};
getBindings = (serverUrl: string, location?: string, forThread = false) => {
let bindings = forThread ?
this.getThreadsBindingsSubject(serverUrl).value.bindings :
this.getBindingsSubject(serverUrl).value;
if (location) {
bindings = bindings.filter((b) => b.location === location);
}
return normalizeBindings(bindings);
};
getCommandForm = (serverUrl: string, key: string, forThread = false) => {
return forThread ?
this.threadCommandForms[serverUrl]?.[key] :
this.commandForms[serverUrl]?.[key];
};
setCommandForm = (serverUrl: string, key: string, form: AppForm, forThread = false) => {
const toStore = forThread ?
this.threadCommandForms :
this.commandForms;
if (!toStore[serverUrl]) {
toStore[serverUrl] = {};
}
toStore[serverUrl][key] = form;
};
}
export default new AppsManager();