From ee89a48464c0e66a844eb5a6dd01854893a650c0 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 24 May 2022 13:28:41 -0300 Subject: [PATCH 1/8] Initial Implementation of Video Conf Providers --- .../accessors/IConfigurationExtend.ts | 4 + .../accessors/IVideoConfProvidersExtend.ts | 15 ++++ src/definition/accessors/index.ts | 1 + src/definition/metadata/AppMethod.ts | 2 + .../videoConfProviders/IVideoConfProvider.ts | 17 ++++ .../videoConfProviders/IVideoConference.ts | 10 +++ .../IVideoConferenceOptions.ts | 4 + .../IVideoConferenceUser.ts | 5 ++ src/definition/videoConfProviders/index.ts | 11 +++ src/server/AppManager.ts | 10 ++- src/server/accessors/ConfigurationExtend.ts | 2 + .../accessors/VideoConfProviderExtend.ts | 12 +++ src/server/accessors/index.ts | 2 + .../AVideoConfProviderAlreadyExistsError.ts | 4 + .../NoVideoConfProviderRegisteredError.ts | 4 + src/server/errors/index.ts | 4 + src/server/managers/AppAccessorManager.ts | 4 +- src/server/managers/AppVideoConfProvider.ts | 88 +++++++++++++++++++ .../managers/AppVideoConfProviderManager.ts | 74 ++++++++++++++++ src/server/managers/index.ts | 2 + src/server/permissions/AppPermissions.ts | 4 + tests/server/AppManager.spec.ts | 3 +- tests/server/accessors/AppAccessors.spec.ts | 5 +- .../accessors/ConfigurationExtend.spec.ts | 11 ++- .../managers/AppAccessorManager.spec.ts | 5 +- tests/server/managers/AppApiManager.spec.ts | 5 +- .../managers/AppSettingsManager.spec.ts | 5 +- .../managers/AppSlashCommandManager.spec.ts | 5 +- 28 files changed, 307 insertions(+), 11 deletions(-) create mode 100644 src/definition/accessors/IVideoConfProvidersExtend.ts create mode 100644 src/definition/videoConfProviders/IVideoConfProvider.ts create mode 100644 src/definition/videoConfProviders/IVideoConference.ts create mode 100644 src/definition/videoConfProviders/IVideoConferenceOptions.ts create mode 100644 src/definition/videoConfProviders/IVideoConferenceUser.ts create mode 100644 src/definition/videoConfProviders/index.ts create mode 100644 src/server/accessors/VideoConfProviderExtend.ts create mode 100644 src/server/errors/AVideoConfProviderAlreadyExistsError.ts create mode 100644 src/server/errors/NoVideoConfProviderRegisteredError.ts create mode 100644 src/server/managers/AppVideoConfProvider.ts create mode 100644 src/server/managers/AppVideoConfProviderManager.ts diff --git a/src/definition/accessors/IConfigurationExtend.ts b/src/definition/accessors/IConfigurationExtend.ts index 756b4e859..ecb36de99 100644 --- a/src/definition/accessors/IConfigurationExtend.ts +++ b/src/definition/accessors/IConfigurationExtend.ts @@ -5,6 +5,7 @@ import { ISchedulerExtend } from './ISchedulerExtend'; import { ISettingsExtend } from './ISettingsExtend'; import { ISlashCommandsExtend } from './ISlashCommandsExtend'; import { IUIExtend } from './IUIExtend'; +import { IVideoConfProvidersExtend } from './IVideoConfProvidersExtend'; /** * This accessor provides methods for declaring the configuration @@ -29,4 +30,7 @@ export interface IConfigurationExtend { readonly scheduler: ISchedulerExtend; /** Accessor for registering different elements in the host UI */ readonly ui: IUIExtend; + + /** Accessor for declaring the videoconf providers which your App provides. */ + readonly videoConfProviders: IVideoConfProvidersExtend; } diff --git a/src/definition/accessors/IVideoConfProvidersExtend.ts b/src/definition/accessors/IVideoConfProvidersExtend.ts new file mode 100644 index 000000000..452f551fa --- /dev/null +++ b/src/definition/accessors/IVideoConfProvidersExtend.ts @@ -0,0 +1,15 @@ +import { IVideoConfProvider } from '../videoConfProviders'; + +/** + * This accessor provides methods for adding videoconf providers. + * It is provided during the initialization of your App + */ + +export interface IVideoConfProvidersExtend { + /** + * Adds a videoconf provider + * + * @param provider the provider information + */ + provideVideoConfProvider(provider: IVideoConfProvider): Promise; +} diff --git a/src/definition/accessors/index.ts b/src/definition/accessors/index.ts index 470fab568..4c7a0d4d5 100644 --- a/src/definition/accessors/index.ts +++ b/src/definition/accessors/index.ts @@ -45,3 +45,4 @@ export * from './IUploadCreator'; export * from './IUploadRead'; export * from './IUserBuilder'; export * from './IUserRead'; +export * from './IVideoConfProvidersExtend'; diff --git a/src/definition/metadata/AppMethod.ts b/src/definition/metadata/AppMethod.ts index d6f04a041..7153e7a29 100644 --- a/src/definition/metadata/AppMethod.ts +++ b/src/definition/metadata/AppMethod.ts @@ -5,6 +5,8 @@ export enum AppMethod { _COMMAND_PREVIEWER = 'previewer', _COMMAND_PREVIEW_EXECUTOR = 'executePreviewItem', _JOB_PROCESSOR = 'jobProcessor', + _VIDEOCONF_GENERATE_URL = 'generateUrl', + _VIDEOCONF_CUSTOMIZE_URL = 'customizeUrl', INITIALIZE = 'initialize', ONENABLE = 'onEnable', ONDISABLE = 'onDisable', diff --git a/src/definition/videoConfProviders/IVideoConfProvider.ts b/src/definition/videoConfProviders/IVideoConfProvider.ts new file mode 100644 index 000000000..4f42ae044 --- /dev/null +++ b/src/definition/videoConfProviders/IVideoConfProvider.ts @@ -0,0 +1,17 @@ +import { IVideoConference } from './IVideoConference'; +import { IVideoConferenceOptions } from './IVideoConferenceOptions'; +import { IVideoConferenceUser } from './IVideoConferenceUser'; + +/** + * Represents a video conference provider + */ +export interface IVideoConfProvider { + /** + * The function which gets called when a new video conference url is requested + */ + generateUrl(callId: string): Promise; + /** + * The function which gets called whenever a user join url is requested + */ + customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise; +} diff --git a/src/definition/videoConfProviders/IVideoConference.ts b/src/definition/videoConfProviders/IVideoConference.ts new file mode 100644 index 000000000..f1e041cac --- /dev/null +++ b/src/definition/videoConfProviders/IVideoConference.ts @@ -0,0 +1,10 @@ +import type { IVideoConferenceUser } from './IVideoConferenceUser'; + +export interface IVideoConference { + _id: string; + type: 'direct' | 'videoconference'; + rid: string; + url: string; + createdBy: IVideoConferenceUser; + title?: string; +} diff --git a/src/definition/videoConfProviders/IVideoConferenceOptions.ts b/src/definition/videoConfProviders/IVideoConferenceOptions.ts new file mode 100644 index 000000000..9dccc73f5 --- /dev/null +++ b/src/definition/videoConfProviders/IVideoConferenceOptions.ts @@ -0,0 +1,4 @@ +export interface IVideoConferenceOptions { + mic?: boolean; + cam?: boolean; +} diff --git a/src/definition/videoConfProviders/IVideoConferenceUser.ts b/src/definition/videoConfProviders/IVideoConferenceUser.ts new file mode 100644 index 000000000..86ab22435 --- /dev/null +++ b/src/definition/videoConfProviders/IVideoConferenceUser.ts @@ -0,0 +1,5 @@ +export interface IVideoConferenceUser { + _id: string; + username: string; + name: string; +} diff --git a/src/definition/videoConfProviders/index.ts b/src/definition/videoConfProviders/index.ts new file mode 100644 index 000000000..f014cd06d --- /dev/null +++ b/src/definition/videoConfProviders/index.ts @@ -0,0 +1,11 @@ +import { IVideoConference } from './IVideoConference'; +import { IVideoConferenceOptions } from './IVideoConferenceOptions'; +import { IVideoConferenceUser } from './IVideoConferenceUser'; +import { IVideoConfProvider } from './IVideoConfProvider'; + +export { + IVideoConference, + IVideoConferenceOptions, + IVideoConferenceUser, + IVideoConfProvider, +}; diff --git a/src/server/AppManager.ts b/src/server/AppManager.ts index 89ea3cf72..63fd08a71 100644 --- a/src/server/AppManager.ts +++ b/src/server/AppManager.ts @@ -11,7 +11,7 @@ import { InvalidLicenseError } from './errors'; import { IGetAppsFilter } from './IGetAppsFilter'; import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppLicenseManager, AppListenerManager, AppSchedulerManager, AppSettingsManager, - AppSlashCommandManager, + AppSlashCommandManager, AppVideoConfProviderManager, } from './managers'; import { UIActionButtonManager } from './managers/UIActionButtonManager'; import { IMarketplaceInfo } from './marketplace'; @@ -60,6 +60,7 @@ export class AppManager { private readonly licenseManager: AppLicenseManager; private readonly schedulerManager: AppSchedulerManager; private readonly uiActionButtonManager: UIActionButtonManager; + private readonly videoConfProviderManager: AppVideoConfProviderManager; private isLoaded: boolean; @@ -106,6 +107,7 @@ export class AppManager { this.licenseManager = new AppLicenseManager(this); this.schedulerManager = new AppSchedulerManager(this); this.uiActionButtonManager = new UIActionButtonManager(this); + this.videoConfProviderManager = new AppVideoConfProviderManager(this); this.isLoaded = false; AppManager.Instance = this; @@ -151,6 +153,10 @@ export class AppManager { return this.commandManager; } + public getVideoConfProviderManager(): AppVideoConfProviderManager { + return this.videoConfProviderManager; + } + public getLicenseManager(): AppLicenseManager { return this.licenseManager; } @@ -870,6 +876,7 @@ export class AppManager { this.accessorManager.purifyApp(app.getID()); await this.schedulerManager.cleanUp(app.getID()); this.uiActionButtonManager.clearAppActionButtons(app.getID()); + this.videoConfProviderManager.unregisterProviders(app.getID()); } /** @@ -936,6 +943,7 @@ export class AppManager { this.apiManager.registerApis(app.getID()); this.listenerManager.registerListeners(app); this.listenerManager.releaseEssentialEvents(app); + this.videoConfProviderManager.registerProviders(app.getID()); } else { await this.purgeAppConfig(app); } diff --git a/src/server/accessors/ConfigurationExtend.ts b/src/server/accessors/ConfigurationExtend.ts index 5e6bad2f6..b656bebf6 100644 --- a/src/server/accessors/ConfigurationExtend.ts +++ b/src/server/accessors/ConfigurationExtend.ts @@ -7,6 +7,7 @@ import { ISettingsExtend, ISlashCommandsExtend, IUIExtend, + IVideoConfProvidersExtend, } from '../../definition/accessors'; export class ConfigurationExtend implements IConfigurationExtend { @@ -18,5 +19,6 @@ export class ConfigurationExtend implements IConfigurationExtend { public readonly externalComponents: IExternalComponentsExtend, public readonly scheduler: ISchedulerExtend, public readonly ui: IUIExtend, + public readonly videoConfProviders: IVideoConfProvidersExtend, ) { } } diff --git a/src/server/accessors/VideoConfProviderExtend.ts b/src/server/accessors/VideoConfProviderExtend.ts new file mode 100644 index 000000000..002072621 --- /dev/null +++ b/src/server/accessors/VideoConfProviderExtend.ts @@ -0,0 +1,12 @@ +import { AppVideoConfProviderManager } from '../managers/AppVideoConfProviderManager'; + +import { IVideoConfProvidersExtend } from '../../definition/accessors'; +import { IVideoConfProvider } from '../../definition/videoConfProviders'; + +export class VideoConfProviderExtend implements IVideoConfProvidersExtend { + constructor(private readonly manager: AppVideoConfProviderManager, private readonly appId: string) { } + + public provideVideoConfProvider(provider: IVideoConfProvider): Promise { + return Promise.resolve(this.manager.addProvider(this.appId, provider)); + } +} diff --git a/src/server/accessors/index.ts b/src/server/accessors/index.ts index 0ca3f47ce..06b512145 100644 --- a/src/server/accessors/index.ts +++ b/src/server/accessors/index.ts @@ -33,6 +33,7 @@ import { SlashCommandsModify } from './SlashCommandsModify'; import { UploadRead } from './UploadRead'; import { UserBuilder } from './UserBuilder'; import { UserRead } from './UserRead'; +import { VideoConfProviderExtend } from './VideoConfProviderExtend'; export { ApiExtend, @@ -70,4 +71,5 @@ export { UserRead, SchedulerExtend, SchedulerModify, + VideoConfProviderExtend, }; diff --git a/src/server/errors/AVideoConfProviderAlreadyExistsError.ts b/src/server/errors/AVideoConfProviderAlreadyExistsError.ts new file mode 100644 index 000000000..5f30df24c --- /dev/null +++ b/src/server/errors/AVideoConfProviderAlreadyExistsError.ts @@ -0,0 +1,4 @@ +export class AVideoConfProviderAlreadyExistsError implements Error { + public name = 'AVideoConfProviderAlreadyExists'; + public message = 'A video conference provider is already registered in the system.'; +} diff --git a/src/server/errors/NoVideoConfProviderRegisteredError.ts b/src/server/errors/NoVideoConfProviderRegisteredError.ts new file mode 100644 index 000000000..850d23704 --- /dev/null +++ b/src/server/errors/NoVideoConfProviderRegisteredError.ts @@ -0,0 +1,4 @@ +export class NoVideoConfProviderRegisteredError implements Error { + public name = 'NoVideoConfProviderRegistered'; + public message = 'There are no video conference providers registered in the system.'; +} diff --git a/src/server/errors/index.ts b/src/server/errors/index.ts index 0837ce92d..76efe4066 100644 --- a/src/server/errors/index.ts +++ b/src/server/errors/index.ts @@ -1,3 +1,4 @@ +import { AVideoConfProviderAlreadyExistsError } from './AVideoConfProviderAlreadyExistsError'; import { CommandAlreadyExistsError } from './CommandAlreadyExistsError'; import { CommandHasAlreadyBeenTouchedError } from './CommandHasAlreadyBeenTouchedError'; import { CompilerError } from './CompilerError'; @@ -5,6 +6,7 @@ import { InvalidLicenseError } from './InvalidLicenseError'; import { MustContainFunctionError } from './MustContainFunctionError'; import { MustExtendAppError } from './MustExtendAppError'; import { NotEnoughMethodArgumentsError } from './NotEnoughMethodArgumentsError'; +import { NoVideoConfProviderRegisteredError } from './NoVideoConfProviderRegisteredError'; import { PathAlreadyExistsError } from './PathAlreadyExistsError'; import { RequiredApiVersionError } from './RequiredApiVersionError'; @@ -18,4 +20,6 @@ export { NotEnoughMethodArgumentsError, RequiredApiVersionError, InvalidLicenseError, + NoVideoConfProviderRegisteredError, + AVideoConfProviderAlreadyExistsError, }; diff --git a/src/server/managers/AppAccessorManager.ts b/src/server/managers/AppAccessorManager.ts index b140030b4..7e023cee0 100644 --- a/src/server/managers/AppAccessorManager.ts +++ b/src/server/managers/AppAccessorManager.ts @@ -35,6 +35,7 @@ import { SlashCommandsModify, UploadRead, UserRead, + VideoConfProviderExtend, } from '../accessors'; import { CloudWorkspaceRead } from '../accessors/CloudWorkspaceRead'; import { UIExtend } from '../accessors/UIExtend'; @@ -87,13 +88,14 @@ export class AppAccessorManager { const htt = new HttpExtend(); const cmds = new SlashCommandsExtend(this.manager.getCommandManager(), appId); + const videoConf = new VideoConfProviderExtend(this.manager.getVideoConfProviderManager(), appId); const apis = new ApiExtend(this.manager.getApiManager(), appId); const sets = new SettingsExtend(rl); const excs = new ExternalComponentsExtend(this.manager.getExternalComponentManager(), appId); const scheduler = new SchedulerExtend(this.manager.getSchedulerManager(), appId); const ui = new UIExtend(this.manager.getUIActionButtonManager(), appId); - this.configExtenders.set(appId, new ConfigurationExtend(htt, sets, cmds, apis, excs, scheduler, ui)); + this.configExtenders.set(appId, new ConfigurationExtend(htt, sets, cmds, apis, excs, scheduler, ui, videoConf)); } return this.configExtenders.get(appId); diff --git a/src/server/managers/AppVideoConfProvider.ts b/src/server/managers/AppVideoConfProvider.ts new file mode 100644 index 000000000..d236d24ff --- /dev/null +++ b/src/server/managers/AppVideoConfProvider.ts @@ -0,0 +1,88 @@ +import { AppMethod } from '../../definition/metadata'; +import type { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; + +import { ProxiedApp } from '../ProxiedApp'; +import { AppLogStorage } from '../storage'; +import { AppAccessorManager } from './AppAccessorManager'; + +export class AppVideoConfProvider { + /** + * States whether this provider has been registered into the Rocket.Chat system or not. + */ + public isRegistered: boolean; + + constructor(public app: ProxiedApp, public provider: IVideoConfProvider) { + this.isRegistered = false; + } + + public hasBeenRegistered(): void { + this.isRegistered = true; + } + + public canBeRan(method: AppMethod): boolean { + return this.app.hasMethod(method); + } + + public async runGenerateUrl( + callId: string, + logStorage: AppLogStorage, + accessors: AppAccessorManager, + ): Promise { + return await this.runTheCode(AppMethod._VIDEOCONF_GENERATE_URL, logStorage, accessors, [callId]); + } + + public async runCustomizeUrl( + call: IVideoConference, + user: IVideoConferenceUser, + options: IVideoConferenceOptions, + logStorage: AppLogStorage, + accessors: AppAccessorManager, + ): Promise { + return await this.runTheCode(AppMethod._VIDEOCONF_CUSTOMIZE_URL, logStorage, accessors, [call, user, options]); + } + + private async runTheCode( + method: AppMethod._VIDEOCONF_GENERATE_URL | AppMethod._VIDEOCONF_CUSTOMIZE_URL, + logStorage: AppLogStorage, + accessors: AppAccessorManager, + runContextArgs: Array, + ): Promise { + // Ensure the provider has the property before going on + if (typeof this.provider[method] !== 'function') { + return; + } + + const runContext = this.app.makeContext({ + provider: this.provider, + args: [ + ...runContextArgs, + accessors.getReader(this.app.getID()), + accessors.getModifier(this.app.getID()), + accessors.getHttp(this.app.getID()), + accessors.getPersistence(this.app.getID()), + ], + }); + + const logger = this.app.setupLogger(method); + logger.debug(`Executing ${ method } on video conference provider...`); + + let result: string | undefined; + try { + const runCode = `provider.${ method }.apply(provider, args)`; + result = await this.app.runInContext(runCode, runContext); + logger.debug(`Video Conference Provider's ${ method } was successfully executed.`); + } catch (e) { + logger.error(e); + logger.debug(`Video Conference Provider's ${ method } was unsuccessful.`); + } + + try { + await logStorage.storeEntries(this.app.getID(), logger); + } catch (e) { + // Don't care, at the moment. + // TODO: Evaluate to determine if we do care + } + + return result; + } +} diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts new file mode 100644 index 000000000..e7223f293 --- /dev/null +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -0,0 +1,74 @@ +import type { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; +import { AppManager } from '../AppManager'; +import { AVideoConfProviderAlreadyExistsError, NoVideoConfProviderRegisteredError } from '../errors'; +import { AppAccessorManager } from './AppAccessorManager'; +import { AppVideoConfProvider } from './AppVideoConfProvider'; + +export class AppVideoConfProviderManager { + private readonly accessors: AppAccessorManager; + + private videoConfProviders: Map; + + constructor(private readonly manager: AppManager) { + this.accessors = this.manager.getAccessorManager(); + + this.videoConfProviders = new Map(); + } + public addProvider(appId: string, provider: IVideoConfProvider): void { + const app = this.manager.getOneById(appId); + if (!app) { + throw new Error('App must exist in order for a video conference provider to be added.'); + } + + this.videoConfProviders.set(appId, new AppVideoConfProvider(app, provider)); + } + + public registerProviders(appId: string): void { + if (!this.videoConfProviders.has(appId)) { + return; + } + + const providerInfo = this.videoConfProviders.get(appId); + + const registeredProviderInfo = this.retrieveRegisteredProvider(); + if (registeredProviderInfo && registeredProviderInfo !== providerInfo) { + throw new AVideoConfProviderAlreadyExistsError(); + } + + providerInfo.hasBeenRegistered(); + } + + public unregisterProviders(appId: string): void { + if (!this.videoConfProviders.has(appId)) { + return; + } + + this.videoConfProviders.get(appId).isRegistered = false; + } + + public async generateUrl(callId: string): Promise { + const providerInfo = this.retrieveRegisteredProvider(); + if (!providerInfo) { + throw new NoVideoConfProviderRegisteredError(); + } + + return providerInfo.runGenerateUrl(callId, this.manager.getLogStorage(), this.accessors); + } + + public async customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise { + const providerInfo = this.retrieveRegisteredProvider(); + if (!providerInfo) { + throw new NoVideoConfProviderRegisteredError(); + } + + return providerInfo.runCustomizeUrl(call, user, options, this.manager.getLogStorage(), this.accessors); + } + + private retrieveRegisteredProvider(): AppVideoConfProvider | undefined { + for (const [, provider] of this.videoConfProviders) { + if (provider.isRegistered) { + return provider; + } + } + } +} diff --git a/src/server/managers/index.ts b/src/server/managers/index.ts index 23f3ba7b8..d8763124b 100644 --- a/src/server/managers/index.ts +++ b/src/server/managers/index.ts @@ -6,6 +6,7 @@ import { AppListenerManager } from './AppListenerManager'; import { AppSchedulerManager } from './AppSchedulerManager'; import { AppSettingsManager } from './AppSettingsManager'; import { AppSlashCommandManager } from './AppSlashCommandManager'; +import { AppVideoConfProviderManager } from './AppVideoConfProviderManager'; export { AppAccessorManager, @@ -16,4 +17,5 @@ export { AppSlashCommandManager, AppApiManager, AppSchedulerManager, + AppVideoConfProviderManager, }; diff --git a/src/server/permissions/AppPermissions.ts b/src/server/permissions/AppPermissions.ts index 2c1b876e7..4dc355084 100644 --- a/src/server/permissions/AppPermissions.ts +++ b/src/server/permissions/AppPermissions.ts @@ -84,6 +84,9 @@ export const AppPermissions = { 'command': { default: { name: 'slashcommand' }, }, + 'videoConfProvider': { + default: { name: 'videoconf-provider' }, + }, 'apis': { default: { name: 'api' }, }, @@ -116,5 +119,6 @@ export const defaultPermissions: Array = [ AppPermissions.persistence.default, AppPermissions.env.read, AppPermissions.command.default, + AppPermissions.videoConfProvider.default, AppPermissions.apis.default, ]; diff --git a/tests/server/AppManager.spec.ts b/tests/server/AppManager.spec.ts index cb1b0599d..b43e9ce82 100644 --- a/tests/server/AppManager.spec.ts +++ b/tests/server/AppManager.spec.ts @@ -5,7 +5,7 @@ import { SimpleClass, TestInfastructureSetup } from '../test-data/utilities'; import { AppManager } from '../../src/server/AppManager'; import { AppBridges } from '../../src/server/bridges'; import { AppCompiler, AppPackageParser } from '../../src/server/compiler'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppListenerManager, AppSettingsManager, AppSlashCommandManager } from '../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppListenerManager, AppSettingsManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../src/server/managers'; import { AppLogStorage, AppMetadataStorage, AppSourceStorage } from '../../src/server/storage'; export class AppManagerTestFixture { @@ -95,5 +95,6 @@ export class AppManagerTestFixture { Expect(manager.getExternalComponentManager() instanceof AppExternalComponentManager).toBe(true); Expect(manager.getApiManager() instanceof AppApiManager).toBe(true); Expect(manager.getSettingsManager() instanceof AppSettingsManager).toBe(true); + Expect(manager.getVideoConfProviderManager() instanceof AppVideoConfProviderManager).toBe(true); } } diff --git a/tests/server/accessors/AppAccessors.spec.ts b/tests/server/accessors/AppAccessors.spec.ts index 7011f988c..caf3fd0c7 100644 --- a/tests/server/accessors/AppAccessors.spec.ts +++ b/tests/server/accessors/AppAccessors.spec.ts @@ -7,7 +7,7 @@ import { AppAccessors } from '../../../src/server/accessors'; import { AppManager } from '../../../src/server/AppManager'; import { AppBridges } from '../../../src/server/bridges'; import { AppConsole } from '../../../src/server/logging'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager } from '../../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; import { ProxiedApp } from '../../../src/server/ProxiedApp'; import { AppLogStorage } from '../../../src/server/storage'; @@ -73,6 +73,9 @@ export class AppAccessorsTestFixture { getUIActionButtonManager() { return {} as UIActionButtonManager; }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, } as AppManager; this.mockAccessors = new AppAccessorManager(this.mockManager); diff --git a/tests/server/accessors/ConfigurationExtend.spec.ts b/tests/server/accessors/ConfigurationExtend.spec.ts index 226a52fba..80b21d38d 100644 --- a/tests/server/accessors/ConfigurationExtend.spec.ts +++ b/tests/server/accessors/ConfigurationExtend.spec.ts @@ -1,5 +1,5 @@ import { Expect, SetupFixture, Test } from 'alsatian'; -import { IApiExtend, IExternalComponentsExtend, IHttpExtend, ISchedulerExtend, ISettingsExtend, ISlashCommandsExtend, IUIExtend } from '../../../src/definition/accessors'; +import { IApiExtend, IExternalComponentsExtend, IHttpExtend, ISchedulerExtend, ISettingsExtend, ISlashCommandsExtend, IUIExtend, IVideoConfProvidersExtend } from '../../../src/definition/accessors'; import { ConfigurationExtend } from '../../../src/server/accessors'; @@ -11,6 +11,7 @@ export class ConfigurationExtendTestFixture { private externalComponent: IExternalComponentsExtend; private schedulerExtend: ISchedulerExtend; private uiExtend: IUIExtend; + private vcProvidersExtend: IVideoConfProvidersExtend; @SetupFixture public setupFixture() { @@ -21,16 +22,20 @@ export class ConfigurationExtendTestFixture { this.externalComponent = {} as IExternalComponentsExtend; this.schedulerExtend = {} as ISchedulerExtend; this.uiExtend = {} as IUIExtend; + this.vcProvidersExtend = {} as IVideoConfProvidersExtend; } @Test() public useConfigurationExtend() { - Expect(() => new ConfigurationExtend(this.he, this.se, this.sce, this.api, this.externalComponent, this.schedulerExtend, this.uiExtend)).not.toThrow(); + Expect(() => new ConfigurationExtend(this.he, this.se, this.sce, this.api, this.externalComponent, + this.schedulerExtend, this.uiExtend, this.vcProvidersExtend)).not.toThrow(); - const se = new ConfigurationExtend(this.he, this.se, this.sce, this.api, this.externalComponent, this.schedulerExtend, this.uiExtend); + const se = new ConfigurationExtend(this.he, this.se, this.sce, this.api, this.externalComponent, + this.schedulerExtend, this.uiExtend, this.vcProvidersExtend); Expect(se.http).toBeDefined(); Expect(se.settings).toBeDefined(); Expect(se.slashCommands).toBeDefined(); Expect(se.externalComponents).toBeDefined(); + Expect(se.videoConfProviders).toBeDefined(); } } diff --git a/tests/server/managers/AppAccessorManager.spec.ts b/tests/server/managers/AppAccessorManager.spec.ts index 81088e5ef..7ed9b1c13 100644 --- a/tests/server/managers/AppAccessorManager.spec.ts +++ b/tests/server/managers/AppAccessorManager.spec.ts @@ -2,7 +2,7 @@ import { Expect, RestorableFunctionSpy, Setup, SetupFixture, SpyOn, Teardown, Te import { AppManager } from '../../../src/server/AppManager'; import { AppBridges } from '../../../src/server/bridges'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager } from '../../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; import { ProxiedApp } from '../../../src/server/ProxiedApp'; import { TestsAppBridges } from '../../test-data/bridges/appBridges'; @@ -39,6 +39,9 @@ export class AppAccessorManagerTestFixture { getUIActionButtonManager() { return {} as UIActionButtonManager; }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, } as AppManager; } diff --git a/tests/server/managers/AppApiManager.spec.ts b/tests/server/managers/AppApiManager.spec.ts index 8137e060a..3f481fb4b 100644 --- a/tests/server/managers/AppApiManager.spec.ts +++ b/tests/server/managers/AppApiManager.spec.ts @@ -10,7 +10,7 @@ import { AppManager } from '../../../src/server/AppManager'; import { AppBridges } from '../../../src/server/bridges'; import { PathAlreadyExistsError } from '../../../src/server/errors'; import { AppConsole } from '../../../src/server/logging'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager } from '../../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; import { AppApi } from '../../../src/server/managers/AppApi'; import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; import { ProxiedApp } from '../../../src/server/ProxiedApp'; @@ -80,6 +80,9 @@ export class AppApiManagerTestFixture { getUIActionButtonManager() { return {} as UIActionButtonManager; }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, } as AppManager; this.mockAccessors = new AppAccessorManager(this.mockManager); diff --git a/tests/server/managers/AppSettingsManager.spec.ts b/tests/server/managers/AppSettingsManager.spec.ts index 6a70d5aba..46f644d2f 100644 --- a/tests/server/managers/AppSettingsManager.spec.ts +++ b/tests/server/managers/AppSettingsManager.spec.ts @@ -6,7 +6,7 @@ import { TestData } from '../../test-data/utilities'; import { AppManager } from '../../../src/server/AppManager'; import { AppBridges } from '../../../src/server/bridges'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSettingsManager, AppSlashCommandManager } from '../../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSettingsManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; import { ProxiedApp } from '../../../src/server/ProxiedApp'; import { AppMetadataStorage, IAppStorageItem } from '../../../src/server/storage'; @@ -80,6 +80,9 @@ export class AppSettingsManagerTestFixture { getUIActionButtonManager() { return {} as UIActionButtonManager; }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, } as AppManager; this.mockAccessors = new AppAccessorManager(this.mockManager); diff --git a/tests/server/managers/AppSlashCommandManager.spec.ts b/tests/server/managers/AppSlashCommandManager.spec.ts index f559d0b9c..17ce88be7 100644 --- a/tests/server/managers/AppSlashCommandManager.spec.ts +++ b/tests/server/managers/AppSlashCommandManager.spec.ts @@ -13,7 +13,7 @@ import { AppManager } from '../../../src/server/AppManager'; import { AppBridges } from '../../../src/server/bridges'; import { CommandAlreadyExistsError, CommandHasAlreadyBeenTouchedError } from '../../../src/server/errors'; import { AppConsole } from '../../../src/server/logging'; -import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager } from '../../../src/server/managers'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; import { AppSlashCommand } from '../../../src/server/managers/AppSlashCommand'; import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; import { ProxiedApp } from '../../../src/server/ProxiedApp'; @@ -81,6 +81,9 @@ export class AppSlashCommandManagerTestFixture { getUIActionButtonManager() { return {} as UIActionButtonManager; }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, } as AppManager; this.mockAccessors = new AppAccessorManager(this.mockManager); From 8a01249246beab9b742f0f4b9733e284e9b5b976 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Thu, 26 May 2022 01:38:11 -0300 Subject: [PATCH 2/8] Accessor tests --- .../accessors/VideoConfProviderExtend.spec.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/server/accessors/VideoConfProviderExtend.spec.ts diff --git a/tests/server/accessors/VideoConfProviderExtend.spec.ts b/tests/server/accessors/VideoConfProviderExtend.spec.ts new file mode 100644 index 000000000..b8313e5ec --- /dev/null +++ b/tests/server/accessors/VideoConfProviderExtend.spec.ts @@ -0,0 +1,44 @@ +// tslint:disable:max-line-length + +import { AsyncTest, Expect, Test } from 'alsatian'; +import { IVideoConfProvider } from '../../../src/definition/videoConfProviders'; + +import { VideoConfProviderExtend } from '../../../src/server/accessors'; +import { AVideoConfProviderAlreadyExistsError } from '../../../src/server/errors'; +import { AppVideoConfProviderManager } from '../../../src/server/managers'; + +export class VideoConfProviderExtendAccessorTestFixture { + @Test() + public basicVideoConfProviderExtend() { + Expect(() => new VideoConfProviderExtend({} as AppVideoConfProviderManager, 'testing')).not.toThrow(); + } + + @AsyncTest() + public async provideProviderToVideoConfProviderExtend(): Promise { + let providerAdded: boolean = false; + const mockManager: AppVideoConfProviderManager = { + addProvider(appId: string, provider: IVideoConfProvider) { + if (providerAdded) { + throw new AVideoConfProviderAlreadyExistsError(); + } + + providerAdded = true; + }, + } as AppVideoConfProviderManager; + + const se = new VideoConfProviderExtend(mockManager, 'testing'); + + const mockProvider: IVideoConfProvider = { + async generateUrl(): Promise { + return ''; + }, + async customizeUrl(): Promise { + return ''; + }, + } as IVideoConfProvider; + + await Expect(async () => await se.provideVideoConfProvider(mockProvider)).not.toThrowAsync(); + Expect(providerAdded).toBe(true); + await Expect(async () => await se.provideVideoConfProvider(mockProvider)).toThrowErrorAsync(AVideoConfProviderAlreadyExistsError, 'A video conference provider is already registered in the system.'); + } +} From 34ec4ea8dbb7aa61ca76c1c7016500b9a8db6cc6 Mon Sep 17 00:00:00 2001 From: thassiov Date: Mon, 30 May 2022 15:17:08 -0300 Subject: [PATCH 3/8] Ensure permission to configure a videoconf provider --- src/server/managers/AppVideoConfProviderManager.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts index e7223f293..d7b24594c 100644 --- a/src/server/managers/AppVideoConfProviderManager.ts +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -1,7 +1,10 @@ import type { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; import { AppManager } from '../AppManager'; import { AVideoConfProviderAlreadyExistsError, NoVideoConfProviderRegisteredError } from '../errors'; +import { PermissionDeniedError } from '../errors/PermissionDeniedError'; +import { AppPermissions } from '../permissions/AppPermissions'; import { AppAccessorManager } from './AppAccessorManager'; +import { AppPermissionManager } from './AppPermissionManager'; import { AppVideoConfProvider } from './AppVideoConfProvider'; export class AppVideoConfProviderManager { @@ -20,6 +23,13 @@ export class AppVideoConfProviderManager { throw new Error('App must exist in order for a video conference provider to be added.'); } + if (!AppPermissionManager.hasPermission(appId, AppPermissions.videoConfProvider.default)) { + throw new PermissionDeniedError({ + appId, + missingPermissions: [AppPermissions.videoConfProvider.default], + }); + } + this.videoConfProviders.set(appId, new AppVideoConfProvider(app, provider)); } From ed32f3767a686139aed24d9cee00ebce52220257 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 30 May 2022 20:25:23 -0300 Subject: [PATCH 4/8] AppVideoConfProviderManager tests --- .../managers/AppVideoConfProviderManager.ts | 4 + .../AppVideoConfProviderManager.spec.ts | 183 ++++++++++++++++++ tests/test-data/utilities.ts | 45 ++++- 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 tests/server/managers/AppVideoConfProviderManager.spec.ts diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts index d7b24594c..23481ea99 100644 --- a/src/server/managers/AppVideoConfProviderManager.ts +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -30,6 +30,10 @@ export class AppVideoConfProviderManager { }); } + if (this.videoConfProviders.has(appId)) { + throw new AVideoConfProviderAlreadyExistsError(); + } + this.videoConfProviders.set(appId, new AppVideoConfProvider(app, provider)); } diff --git a/tests/server/managers/AppVideoConfProviderManager.spec.ts b/tests/server/managers/AppVideoConfProviderManager.spec.ts new file mode 100644 index 000000000..e6bcf97f8 --- /dev/null +++ b/tests/server/managers/AppVideoConfProviderManager.spec.ts @@ -0,0 +1,183 @@ +import { AsyncTest, Expect, Setup, SetupFixture, Teardown, Test } from 'alsatian'; +import { TestsAppBridges } from '../../test-data/bridges/appBridges'; +import { TestsAppLogStorage } from '../../test-data/storage/logStorage'; +import { TestData } from '../../test-data/utilities'; + +import { AppManager } from '../../../src/server/AppManager'; +import { AppBridges } from '../../../src/server/bridges'; +import { AVideoConfProviderAlreadyExistsError, NoVideoConfProviderRegisteredError } from '../../../src/server/errors'; +import { AppAccessorManager, AppApiManager, AppExternalComponentManager, AppSchedulerManager, AppSlashCommandManager, AppVideoConfProviderManager } from '../../../src/server/managers'; +import { AppVideoConfProvider } from '../../../src/server/managers/AppVideoConfProvider'; +import { UIActionButtonManager } from '../../../src/server/managers/UIActionButtonManager'; +import { ProxiedApp } from '../../../src/server/ProxiedApp'; +import { AppLogStorage } from '../../../src/server/storage'; + +export class AppVideoConfProviderManagerTestFixture { + public static doThrow: boolean = false; + private mockBridges: TestsAppBridges; + private mockApp: ProxiedApp; + private mockAccessors: AppAccessorManager; + private mockManager: AppManager; + + @SetupFixture + public setupFixture() { + this.mockBridges = new TestsAppBridges(); + + this.mockApp = TestData.getMockApp('testing', 'testing'); + + const bri = this.mockBridges; + const app = this.mockApp; + this.mockManager = { + getBridges(): AppBridges { + return bri; + }, + getCommandManager() { + return {} as AppSlashCommandManager; + }, + getExternalComponentManager(): AppExternalComponentManager { + return {} as AppExternalComponentManager; + }, + getApiManager() { + return {} as AppApiManager; + }, + getOneById(appId: string): ProxiedApp { + return appId === 'failMePlease' ? undefined : app; + }, + getLogStorage(): AppLogStorage { + return new TestsAppLogStorage(); + }, + getSchedulerManager() { + return {} as AppSchedulerManager; + }, + getUIActionButtonManager() { + return {} as UIActionButtonManager; + }, + getVideoConfProviderManager() { + return {} as AppVideoConfProviderManager; + }, + } as AppManager; + + this.mockAccessors = new AppAccessorManager(this.mockManager); + const ac = this.mockAccessors; + this.mockManager.getAccessorManager = function _getAccessorManager(): AppAccessorManager { + return ac; + }; + } + + @Setup + public setup() { + } + + @Teardown + public teardown() { + } + + @Test() + public basicAppVideoConfProviderManager() { + Expect(() => new AppVideoConfProviderManager({} as AppManager)).toThrow(); + Expect(() => new AppVideoConfProviderManager(this.mockManager)).not.toThrow(); + + const manager = new AppVideoConfProviderManager(this.mockManager); + Expect((manager as any).manager).toBe(this.mockManager); + Expect((manager as any).accessors).toBe(this.mockManager.getAccessorManager()); + Expect((manager as any).videoConfProviders).toBeDefined(); + Expect((manager as any).videoConfProviders.size).toBe(0); + } + + @Test() + public aaddProvider() { + const provider = TestData.getVideoConfProvider(); + const manager = new AppVideoConfProviderManager(this.mockManager); + + Expect(() => manager.addProvider('testing', provider)).not.toThrow(); + Expect((manager as any).videoConfProviders.size).toBe(1); + Expect(() => manager.addProvider('testing', provider)) + .toThrowError(AVideoConfProviderAlreadyExistsError, 'A video conference provider is already registered in the system.'); + Expect(() => manager.addProvider('failMePlease', provider)) + .toThrowError(Error, 'App must exist in order for a video conference provider to be added.'); + Expect((manager as any).videoConfProviders.size).toBe(1); + } + + @Test() + public registerProviders() { + const manager = new AppVideoConfProviderManager(this.mockManager); + + manager.addProvider('testing', TestData.getVideoConfProvider()); + const firstRegInfo = (manager as any).videoConfProviders.get('testing') as AppVideoConfProvider; + + manager.addProvider('testing2', TestData.getVideoConfProvider()); + const secondRegInfo = (manager as any).videoConfProviders.get('testing2') as AppVideoConfProvider; + + Expect(() => manager.registerProviders('non-existant')).not.toThrow(); + Expect(() => manager.registerProviders('testing')).not.toThrow(); + Expect(firstRegInfo.isRegistered).toBe(true); + Expect(secondRegInfo.isRegistered).toBe(false); + + Expect(() => manager.registerProviders('testing2')) + .toThrowError(AVideoConfProviderAlreadyExistsError, 'A video conference provider is already registered in the system.'); + } + + @Test() + public unregisterProviders() { + const manager = new AppVideoConfProviderManager(this.mockManager); + + manager.addProvider('testing', TestData.getVideoConfProvider()); + const regInfo = (manager as any).videoConfProviders.get('testing') as AppVideoConfProvider; + Expect(() => manager.registerProviders('testing')).not.toThrow(); + + Expect(() => manager.unregisterProviders('non-existant')).not.toThrow(); + Expect(regInfo.isRegistered).toBe(true); + Expect(() => manager.unregisterProviders('testing')).not.toThrow(); + Expect(regInfo.isRegistered).toBe(false); + } + + @AsyncTest() + public async failToGenerateUrlWithoutProvider() { + const manager = new AppVideoConfProviderManager(this.mockManager); + + await Expect(async () => manager.generateUrl('callId')) + .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); + + manager.addProvider('testing', TestData.getVideoConfProvider()); + + await Expect(async () => await manager.generateUrl('callId')) + .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); + } + + @AsyncTest() + public async generateUrl() { + const manager = new AppVideoConfProviderManager(this.mockManager); + manager.addProvider('testing', TestData.getVideoConfProvider()); + manager.registerProviders('testing'); + + const url = await manager.generateUrl('first-call'); + await Expect(url).toBe('video-conf/first-call'); + } + + @AsyncTest() + public async failToCustomizeUrlWithoutProvider() { + const manager = new AppVideoConfProviderManager(this.mockManager); + const call = TestData.getVideoConference(); + const user = TestData.getVideoConferenceUser(); + + await Expect(async () => await manager.customizeUrl(call, user, {})) + .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); + + manager.addProvider('testing', TestData.getVideoConfProvider()); + + await Expect(async () => await manager.customizeUrl(call, user, {})) + .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); + } + + @AsyncTest() + public async customizeUrl() { + const manager = new AppVideoConfProviderManager(this.mockManager); + manager.addProvider('testing', TestData.getVideoConfProvider()); + manager.registerProviders('testing'); + + const call = TestData.getVideoConference(); + const user = TestData.getVideoConferenceUser(); + + await Expect(await manager.customizeUrl(call, user, {})).toBe('video-conf/first-call#caller'); + } +} diff --git a/tests/test-data/utilities.ts b/tests/test-data/utilities.ts index b7c0336da..fca04ee04 100644 --- a/tests/test-data/utilities.ts +++ b/tests/test-data/utilities.ts @@ -14,8 +14,13 @@ import { TestSourceStorage } from './storage/TestSourceStorage'; import { ApiSecurity, ApiVisibility, IApi, IApiRequest, IApiResponse } from '../../src/definition/api'; import { IApiEndpointInfo } from '../../src/definition/api/IApiEndpointInfo'; +import { App } from '../../src/definition/App'; +import { AppStatus } from '../../src/definition/AppStatus'; +import { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../src/definition/videoConfProviders'; +import { AppManager } from '../../src/server/AppManager'; import { AppBridges } from '../../src/server/bridges'; -import { AppLogStorage, AppMetadataStorage, AppSourceStorage } from '../../src/server/storage'; +import { ProxiedApp } from '../../src/server/ProxiedApp'; +import { AppLogStorage, AppMetadataStorage, AppSourceStorage, IAppStorageItem } from '../../src/server/storage'; export class TestInfastructureSetup { private appStorage: TestsAppStorage; @@ -185,6 +190,44 @@ export class TestData { }], }; } + + public static getVideoConfProvider(): IVideoConfProvider { + return { + async generateUrl(callId: string): Promise { + return `video-conf/${callId}`; + }, + + async customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise { + return `video-conf/${call._id}#${user.username}`; + }, + }; + } + + public static getVideoConferenceUser(): IVideoConferenceUser { + return { + _id: 'callerId', + username: 'caller', + name: 'John Caller', + }; + } + + public static getVideoConference(): IVideoConference { + return { + _id: 'first-call', + type: 'videoconference', + rid: 'roomId', + url: 'video-conf/first-call', + createdBy: this.getVideoConferenceUser(), + title: 'Test Call', + }; + } + + public static getMockApp(id: string, name: string): ProxiedApp { + return new ProxiedApp({} as AppManager, { status: AppStatus.UNKNOWN } as IAppStorageItem, { + getName() { return 'testing'; }, + getID() { return 'testing'; }, + } as App, (mod: string) => mod); + } } export class SimpleClass { From f2bf96ea3341d8697f6608bb0d0690ea2f8147f4 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 30 May 2022 20:30:10 -0300 Subject: [PATCH 5/8] AppVideoConfProvider tests --- .../managers/AppVideoConfProvider.spec.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/server/managers/AppVideoConfProvider.spec.ts diff --git a/tests/server/managers/AppVideoConfProvider.spec.ts b/tests/server/managers/AppVideoConfProvider.spec.ts new file mode 100644 index 000000000..a57b16576 --- /dev/null +++ b/tests/server/managers/AppVideoConfProvider.spec.ts @@ -0,0 +1,33 @@ +import { Expect, SetupFixture, Test } from 'alsatian'; +import { AppMethod } from '../../../src/definition/metadata'; +import { IVideoConfProvider } from '../../../src/definition/videoConfProviders'; + +import { AppVideoConfProvider } from '../../../src/server/managers/AppVideoConfProvider'; +import { ProxiedApp } from '../../../src/server/ProxiedApp'; + +export class AppSlashCommandRegistrationTestFixture { + private mockApp: ProxiedApp; + + @SetupFixture + public setupFixture() { + this.mockApp = { + hasMethod(method: AppMethod): boolean { + return true; + }, + } as ProxiedApp; + } + + @Test() + public ensureAppVideoConfManager() { + Expect(() => new AppVideoConfProvider(this.mockApp, {} as IVideoConfProvider)).not.toThrow(); + + const ascr = new AppVideoConfProvider(this.mockApp, {} as IVideoConfProvider); + Expect(ascr.isRegistered).toBe(false); + + ascr.hasBeenRegistered(); + Expect(ascr.isRegistered).toBe(true); + + Expect(ascr.canBeRan(AppMethod._VIDEOCONF_GENERATE_URL)).toBe(true); + Expect(ascr.canBeRan(AppMethod._VIDEOCONF_CUSTOMIZE_URL)).toBe(true); + } +} From d950487bd99f31d84c6343f8c4387edd7a441e23 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 30 May 2022 20:45:23 -0300 Subject: [PATCH 6/8] Optional user --- src/definition/videoConfProviders/IVideoConfProvider.ts | 2 +- src/server/managers/AppVideoConfProvider.ts | 4 ++-- src/server/managers/AppVideoConfProviderManager.ts | 2 +- tests/server/managers/AppVideoConfProviderManager.spec.ts | 1 + tests/test-data/utilities.ts | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/definition/videoConfProviders/IVideoConfProvider.ts b/src/definition/videoConfProviders/IVideoConfProvider.ts index 4f42ae044..408ac25ae 100644 --- a/src/definition/videoConfProviders/IVideoConfProvider.ts +++ b/src/definition/videoConfProviders/IVideoConfProvider.ts @@ -13,5 +13,5 @@ export interface IVideoConfProvider { /** * The function which gets called whenever a user join url is requested */ - customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise; + customizeUrl(call: IVideoConference, user?: IVideoConferenceUser, options?: IVideoConferenceOptions): Promise; } diff --git a/src/server/managers/AppVideoConfProvider.ts b/src/server/managers/AppVideoConfProvider.ts index d236d24ff..6f0491dda 100644 --- a/src/server/managers/AppVideoConfProvider.ts +++ b/src/server/managers/AppVideoConfProvider.ts @@ -33,8 +33,8 @@ export class AppVideoConfProvider { public async runCustomizeUrl( call: IVideoConference, - user: IVideoConferenceUser, - options: IVideoConferenceOptions, + user: IVideoConferenceUser | undefined, + options: IVideoConferenceOptions = {}, logStorage: AppLogStorage, accessors: AppAccessorManager, ): Promise { diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts index 23481ea99..5b71dbbc4 100644 --- a/src/server/managers/AppVideoConfProviderManager.ts +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -69,7 +69,7 @@ export class AppVideoConfProviderManager { return providerInfo.runGenerateUrl(callId, this.manager.getLogStorage(), this.accessors); } - public async customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise { + public async customizeUrl(call: IVideoConference, user?: IVideoConferenceUser, options?: IVideoConferenceOptions): Promise { const providerInfo = this.retrieveRegisteredProvider(); if (!providerInfo) { throw new NoVideoConfProviderRegisteredError(); diff --git a/tests/server/managers/AppVideoConfProviderManager.spec.ts b/tests/server/managers/AppVideoConfProviderManager.spec.ts index e6bcf97f8..3e96f3713 100644 --- a/tests/server/managers/AppVideoConfProviderManager.spec.ts +++ b/tests/server/managers/AppVideoConfProviderManager.spec.ts @@ -179,5 +179,6 @@ export class AppVideoConfProviderManagerTestFixture { const user = TestData.getVideoConferenceUser(); await Expect(await manager.customizeUrl(call, user, {})).toBe('video-conf/first-call#caller'); + await Expect(await manager.customizeUrl(call, undefined, {})).toBe('video-conf/first-call#'); } } diff --git a/tests/test-data/utilities.ts b/tests/test-data/utilities.ts index fca04ee04..2f4845652 100644 --- a/tests/test-data/utilities.ts +++ b/tests/test-data/utilities.ts @@ -197,8 +197,8 @@ export class TestData { return `video-conf/${callId}`; }, - async customizeUrl(call: IVideoConference, user: IVideoConferenceUser, options: IVideoConferenceOptions): Promise { - return `video-conf/${call._id}#${user.username}`; + async customizeUrl(call: IVideoConference, user: IVideoConferenceUser | undefined, options: IVideoConferenceOptions): Promise { + return `video-conf/${call._id}#${user ? user.username : ''}`; }, }; } From fff0191b36d144c8de53f0314eb1105c1eda205b Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 30 May 2022 22:57:27 -0300 Subject: [PATCH 7/8] Preventing a provider from being added twice caused issues when disabling and re-enabling apps. --- src/server/managers/AppVideoConfProviderManager.ts | 4 ---- tests/server/managers/AppVideoConfProviderManager.spec.ts | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts index 5b71dbbc4..d4df45453 100644 --- a/src/server/managers/AppVideoConfProviderManager.ts +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -30,10 +30,6 @@ export class AppVideoConfProviderManager { }); } - if (this.videoConfProviders.has(appId)) { - throw new AVideoConfProviderAlreadyExistsError(); - } - this.videoConfProviders.set(appId, new AppVideoConfProvider(app, provider)); } diff --git a/tests/server/managers/AppVideoConfProviderManager.spec.ts b/tests/server/managers/AppVideoConfProviderManager.spec.ts index 3e96f3713..0f084fe03 100644 --- a/tests/server/managers/AppVideoConfProviderManager.spec.ts +++ b/tests/server/managers/AppVideoConfProviderManager.spec.ts @@ -85,14 +85,12 @@ export class AppVideoConfProviderManagerTestFixture { } @Test() - public aaddProvider() { + public addProvider() { const provider = TestData.getVideoConfProvider(); const manager = new AppVideoConfProviderManager(this.mockManager); Expect(() => manager.addProvider('testing', provider)).not.toThrow(); Expect((manager as any).videoConfProviders.size).toBe(1); - Expect(() => manager.addProvider('testing', provider)) - .toThrowError(AVideoConfProviderAlreadyExistsError, 'A video conference provider is already registered in the system.'); Expect(() => manager.addProvider('failMePlease', provider)) .toThrowError(Error, 'App must exist in order for a video conference provider to be added.'); Expect((manager as any).videoConfProviders.size).toBe(1); From c5bc2f126001143e1b2337850e2fb2f23ab16005 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 31 May 2022 01:57:11 -0300 Subject: [PATCH 8/8] Extra params for new url --- .../videoConfProviders/IVideoConfProvider.ts | 4 ++-- src/definition/videoConfProviders/IVideoConference.ts | 7 +++++-- src/definition/videoConfProviders/index.ts | 3 ++- src/server/managers/AppVideoConfProvider.ts | 6 +++--- src/server/managers/AppVideoConfProviderManager.ts | 6 +++--- .../managers/AppVideoConfProviderManager.spec.ts | 10 +++++++--- tests/test-data/utilities.ts | 6 +++--- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/definition/videoConfProviders/IVideoConfProvider.ts b/src/definition/videoConfProviders/IVideoConfProvider.ts index 408ac25ae..bb2365475 100644 --- a/src/definition/videoConfProviders/IVideoConfProvider.ts +++ b/src/definition/videoConfProviders/IVideoConfProvider.ts @@ -1,4 +1,4 @@ -import { IVideoConference } from './IVideoConference'; +import { INewVideoConference, IVideoConference } from './IVideoConference'; import { IVideoConferenceOptions } from './IVideoConferenceOptions'; import { IVideoConferenceUser } from './IVideoConferenceUser'; @@ -9,7 +9,7 @@ export interface IVideoConfProvider { /** * The function which gets called when a new video conference url is requested */ - generateUrl(callId: string): Promise; + generateUrl(call: INewVideoConference): Promise; /** * The function which gets called whenever a user join url is requested */ diff --git a/src/definition/videoConfProviders/IVideoConference.ts b/src/definition/videoConfProviders/IVideoConference.ts index f1e041cac..5adc61421 100644 --- a/src/definition/videoConfProviders/IVideoConference.ts +++ b/src/definition/videoConfProviders/IVideoConference.ts @@ -1,10 +1,13 @@ import type { IVideoConferenceUser } from './IVideoConferenceUser'; -export interface IVideoConference { +export interface INewVideoConference { _id: string; type: 'direct' | 'videoconference'; rid: string; - url: string; createdBy: IVideoConferenceUser; title?: string; } + +export interface IVideoConference extends INewVideoConference { + url: string; +} diff --git a/src/definition/videoConfProviders/index.ts b/src/definition/videoConfProviders/index.ts index f014cd06d..3653718c8 100644 --- a/src/definition/videoConfProviders/index.ts +++ b/src/definition/videoConfProviders/index.ts @@ -1,9 +1,10 @@ -import { IVideoConference } from './IVideoConference'; +import { INewVideoConference, IVideoConference } from './IVideoConference'; import { IVideoConferenceOptions } from './IVideoConferenceOptions'; import { IVideoConferenceUser } from './IVideoConferenceUser'; import { IVideoConfProvider } from './IVideoConfProvider'; export { + INewVideoConference, IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, diff --git a/src/server/managers/AppVideoConfProvider.ts b/src/server/managers/AppVideoConfProvider.ts index 6f0491dda..365c13a68 100644 --- a/src/server/managers/AppVideoConfProvider.ts +++ b/src/server/managers/AppVideoConfProvider.ts @@ -1,5 +1,5 @@ import { AppMethod } from '../../definition/metadata'; -import type { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; +import type { INewVideoConference, IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; import { ProxiedApp } from '../ProxiedApp'; import { AppLogStorage } from '../storage'; @@ -24,11 +24,11 @@ export class AppVideoConfProvider { } public async runGenerateUrl( - callId: string, + call: INewVideoConference, logStorage: AppLogStorage, accessors: AppAccessorManager, ): Promise { - return await this.runTheCode(AppMethod._VIDEOCONF_GENERATE_URL, logStorage, accessors, [callId]); + return await this.runTheCode(AppMethod._VIDEOCONF_GENERATE_URL, logStorage, accessors, [call]); } public async runCustomizeUrl( diff --git a/src/server/managers/AppVideoConfProviderManager.ts b/src/server/managers/AppVideoConfProviderManager.ts index d4df45453..e431ac55c 100644 --- a/src/server/managers/AppVideoConfProviderManager.ts +++ b/src/server/managers/AppVideoConfProviderManager.ts @@ -1,4 +1,4 @@ -import type { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; +import type { INewVideoConference, IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../definition/videoConfProviders'; import { AppManager } from '../AppManager'; import { AVideoConfProviderAlreadyExistsError, NoVideoConfProviderRegisteredError } from '../errors'; import { PermissionDeniedError } from '../errors/PermissionDeniedError'; @@ -56,13 +56,13 @@ export class AppVideoConfProviderManager { this.videoConfProviders.get(appId).isRegistered = false; } - public async generateUrl(callId: string): Promise { + public async generateUrl(call: INewVideoConference): Promise { const providerInfo = this.retrieveRegisteredProvider(); if (!providerInfo) { throw new NoVideoConfProviderRegisteredError(); } - return providerInfo.runGenerateUrl(callId, this.manager.getLogStorage(), this.accessors); + return providerInfo.runGenerateUrl(call, this.manager.getLogStorage(), this.accessors); } public async customizeUrl(call: IVideoConference, user?: IVideoConferenceUser, options?: IVideoConferenceOptions): Promise { diff --git a/tests/server/managers/AppVideoConfProviderManager.spec.ts b/tests/server/managers/AppVideoConfProviderManager.spec.ts index 0f084fe03..160057312 100644 --- a/tests/server/managers/AppVideoConfProviderManager.spec.ts +++ b/tests/server/managers/AppVideoConfProviderManager.spec.ts @@ -133,12 +133,14 @@ export class AppVideoConfProviderManagerTestFixture { public async failToGenerateUrlWithoutProvider() { const manager = new AppVideoConfProviderManager(this.mockManager); - await Expect(async () => manager.generateUrl('callId')) + const call = TestData.getVideoConference(); + + await Expect(async () => manager.generateUrl(call)) .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); manager.addProvider('testing', TestData.getVideoConfProvider()); - await Expect(async () => await manager.generateUrl('callId')) + await Expect(async () => await manager.generateUrl(call)) .toThrowErrorAsync(NoVideoConfProviderRegisteredError, 'There are no video conference providers registered in the system.'); } @@ -148,7 +150,9 @@ export class AppVideoConfProviderManagerTestFixture { manager.addProvider('testing', TestData.getVideoConfProvider()); manager.registerProviders('testing'); - const url = await manager.generateUrl('first-call'); + const call = TestData.getVideoConference(); + + const url = await manager.generateUrl(call); await Expect(url).toBe('video-conf/first-call'); } diff --git a/tests/test-data/utilities.ts b/tests/test-data/utilities.ts index 2f4845652..33241ede3 100644 --- a/tests/test-data/utilities.ts +++ b/tests/test-data/utilities.ts @@ -16,7 +16,7 @@ import { ApiSecurity, ApiVisibility, IApi, IApiRequest, IApiResponse } from '../ import { IApiEndpointInfo } from '../../src/definition/api/IApiEndpointInfo'; import { App } from '../../src/definition/App'; import { AppStatus } from '../../src/definition/AppStatus'; -import { IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../src/definition/videoConfProviders'; +import { INewVideoConference, IVideoConference, IVideoConferenceOptions, IVideoConferenceUser, IVideoConfProvider } from '../../src/definition/videoConfProviders'; import { AppManager } from '../../src/server/AppManager'; import { AppBridges } from '../../src/server/bridges'; import { ProxiedApp } from '../../src/server/ProxiedApp'; @@ -193,8 +193,8 @@ export class TestData { public static getVideoConfProvider(): IVideoConfProvider { return { - async generateUrl(callId: string): Promise { - return `video-conf/${callId}`; + async generateUrl(call: INewVideoConference): Promise { + return `video-conf/${call._id}`; }, async customizeUrl(call: IVideoConference, user: IVideoConferenceUser | undefined, options: IVideoConferenceOptions): Promise {