forked from Ivasoft/mattermost-mobile
219 lines
8.4 KiB
TypeScript
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();
|