404 lines
13 KiB
TypeScript
404 lines
13 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import { CoreSite } from '@classes/sites/site';
|
|
import { CoreLogger } from '@singletons/logger';
|
|
|
|
/**
|
|
* Superclass to help creating delegates
|
|
*/
|
|
export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
|
|
|
|
/**
|
|
* Logger instance.
|
|
*/
|
|
protected logger: CoreLogger;
|
|
|
|
/**
|
|
* List of registered handlers.
|
|
*/
|
|
protected handlers: { [s: string]: HandlerType } = {};
|
|
|
|
/**
|
|
* List of registered handlers enabled for the current site.
|
|
*/
|
|
protected enabledHandlers: { [s: string]: HandlerType } = {};
|
|
|
|
/**
|
|
* Default handler
|
|
*/
|
|
protected defaultHandler?: HandlerType;
|
|
|
|
/**
|
|
* Time when last updateHandler functions started.
|
|
*/
|
|
protected lastUpdateHandlersStart = 0;
|
|
|
|
/**
|
|
* Feature prefix to check is feature is enabled or disabled in site.
|
|
* This check is only made if not false. Override on the subclass or override isFeatureDisabled function.
|
|
*/
|
|
protected featurePrefix?: string;
|
|
|
|
/**
|
|
* Name of the property to be used to index the handlers. By default, the handler's name will be used.
|
|
* If your delegate uses a Moodle component name to identify the handlers, please override this property.
|
|
* E.g. CoreCourseModuleDelegate uses 'modName' to index the handlers.
|
|
*/
|
|
protected handlerNameProperty = 'name';
|
|
|
|
/**
|
|
* Set of promises to update a handler, to prevent doing the same operation twice.
|
|
*/
|
|
protected updatePromises: {[siteId: string]: {[name: string]: Promise<void>}} = {};
|
|
|
|
/**
|
|
* Whether handlers have been initialized.
|
|
*/
|
|
protected handlersInitialized = false;
|
|
|
|
/**
|
|
* Promise to wait for handlers to be initialized.
|
|
*/
|
|
protected handlersInitPromise: Promise<void>;
|
|
|
|
/**
|
|
* Function to resolve the handlers init promise.
|
|
*/
|
|
protected handlersInitResolve!: () => void;
|
|
|
|
/**
|
|
* Constructor of the Delegate.
|
|
*
|
|
* @param delegateName Delegate name used for logging purposes.
|
|
* @param listenSiteEvents Whether to update the handler when a site event occurs (login, site updated, ...).
|
|
*/
|
|
constructor(delegateName: string, listenSiteEvents: boolean = true) {
|
|
this.logger = CoreLogger.getInstance(delegateName);
|
|
|
|
this.handlersInitPromise = new Promise((resolve): void => {
|
|
this.handlersInitResolve = resolve;
|
|
});
|
|
|
|
if (listenSiteEvents) {
|
|
// Update handlers on this cases.
|
|
CoreEvents.on(CoreEvents.LOGIN, () => this.updateHandlers());
|
|
CoreEvents.on(CoreEvents.SITE_UPDATED, () => this.updateHandlers());
|
|
CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => this.updateHandlers());
|
|
CoreEvents.on(CoreEvents.SITE_POLICY_AGREED, (data) => {
|
|
if (data.siteId === CoreSites.getCurrentSiteId()) {
|
|
this.updateHandlers();
|
|
}
|
|
});
|
|
CoreEvents.on(CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED, (data) => {
|
|
if (data.siteId === CoreSites.getCurrentSiteId()) {
|
|
this.updateHandlers();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a certain function in a enabled handler.
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
*
|
|
* @param handlerName The handler name.
|
|
* @param fnName Name of the function to execute.
|
|
* @param params Parameters to pass to the function.
|
|
* @returns Function returned value or default value.
|
|
*/
|
|
protected executeFunctionOnEnabled<T = unknown>(handlerName: string, fnName: string, params?: unknown[]): T | undefined {
|
|
return this.execute<T>(this.enabledHandlers[handlerName], fnName, params);
|
|
}
|
|
|
|
/**
|
|
* Execute a certain function in a handler.
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
*
|
|
* @param handlerName The handler name.
|
|
* @param fnName Name of the function to execute.
|
|
* @param params Parameters to pass to the function.
|
|
* @returns Function returned value or default value.
|
|
*/
|
|
protected executeFunction<T = unknown>(handlerName: string, fnName: string, params?: unknown[]): T | undefined {
|
|
return this.execute<T>(this.handlers[handlerName], fnName, params);
|
|
}
|
|
|
|
/**
|
|
* Execute a certain function in a handler.
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
*
|
|
* @param handler The handler.
|
|
* @param fnName Name of the function to execute.
|
|
* @param params Parameters to pass to the function.
|
|
* @returns Function returned value or default value.
|
|
*/
|
|
private execute<T = unknown>(handler: HandlerType, fnName: string, params?: unknown[]): T | undefined {
|
|
if (handler && handler[fnName]) {
|
|
return handler[fnName].apply(handler, params);
|
|
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
|
return this.defaultHandler[fnName].apply(this.defaultHandler, params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a handler.
|
|
*
|
|
* @param handlerName The handler name.
|
|
* @param enabled Only enabled, or any.
|
|
* @returns Handler.
|
|
*/
|
|
protected getHandler(handlerName: string, enabled: boolean = false): HandlerType {
|
|
return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
|
}
|
|
|
|
/**
|
|
* Gets the handler full name for a given name. This is useful when the handlerNameProperty is different than "name".
|
|
* E.g. blocks are indexed by blockName. If you call this function passing the blockName it will return the name.
|
|
*
|
|
* @param name Name used to indentify the handler.
|
|
* @returns Full name of corresponding handler.
|
|
*/
|
|
getHandlerName(name: string): string {
|
|
const handler = this.getHandler(name, true);
|
|
|
|
if (!handler) {
|
|
return '';
|
|
}
|
|
|
|
return handler.name;
|
|
}
|
|
|
|
/**
|
|
* Check if function exists on a handler.
|
|
*
|
|
* @param handlerName The handler name.
|
|
* @param fnName Name of the function to execute.
|
|
* @param onlyEnabled If check only enabled handlers or all.
|
|
* @returns Function returned value or default value.
|
|
*/
|
|
protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): boolean {
|
|
const handler = onlyEnabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
|
|
|
return handler && typeof handler[fnName] == 'function';
|
|
}
|
|
|
|
/**
|
|
* Check if a handler name has a registered handler (not necessarily enabled).
|
|
*
|
|
* @param name The handler name.
|
|
* @param enabled Only enabled, or any.
|
|
* @returns If the handler is registered or not.
|
|
*/
|
|
hasHandler(name: string, enabled: boolean = false): boolean {
|
|
return enabled ? this.enabledHandlers[name] !== undefined : this.handlers[name] !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Check if the delegate has at least 1 registered handler (not necessarily enabled).
|
|
*
|
|
* @returns If there is at least 1 handler.
|
|
*/
|
|
hasHandlers(): boolean {
|
|
return Object.keys(this.handlers).length > 0;
|
|
}
|
|
|
|
/**
|
|
* Check if a time belongs to the last update handlers call.
|
|
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
|
*
|
|
* @param time Time to check.
|
|
* @returns Whether it's the last call.
|
|
*/
|
|
isLastUpdateCall(time: number): boolean {
|
|
if (!this.lastUpdateHandlersStart) {
|
|
return true;
|
|
}
|
|
|
|
return time == this.lastUpdateHandlersStart;
|
|
}
|
|
|
|
/**
|
|
* Register a handler.
|
|
*
|
|
* @param handler The handler delegate object to register.
|
|
* @returns True when registered, false if already registered.
|
|
*/
|
|
registerHandler(handler: HandlerType): boolean {
|
|
const key = handler[this.handlerNameProperty] || handler.name;
|
|
|
|
if (this.handlers[key] !== undefined) {
|
|
this.logger.log(`Handler '${handler[this.handlerNameProperty]}' already registered`);
|
|
|
|
return false;
|
|
}
|
|
|
|
this.logger.log(`Registered handler '${handler[this.handlerNameProperty]}'`);
|
|
this.handlers[key] = handler;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update the handler for the current site.
|
|
*
|
|
* @param handler The handler to check.
|
|
* @returns Resolved when done.
|
|
*/
|
|
protected updateHandler(handler: HandlerType): Promise<void> {
|
|
const siteId = CoreSites.getCurrentSiteId();
|
|
const currentSite = CoreSites.getCurrentSite();
|
|
let promise: Promise<boolean>;
|
|
|
|
if (this.updatePromises[siteId] && this.updatePromises[siteId][handler.name] !== undefined) {
|
|
// There's already an update ongoing for this handler, return the promise.
|
|
return this.updatePromises[siteId][handler.name];
|
|
} else if (!this.updatePromises[siteId]) {
|
|
this.updatePromises[siteId] = {};
|
|
}
|
|
|
|
if (!currentSite || this.isFeatureDisabled(handler, currentSite)) {
|
|
promise = Promise.resolve(false);
|
|
} else {
|
|
promise = Promise.resolve(handler.isEnabled()).catch(() => false);
|
|
}
|
|
|
|
// Checks if the handler is enabled.
|
|
this.updatePromises[siteId][handler.name] = promise.then((enabled: boolean) => {
|
|
// Check that site hasn't changed since the check started.
|
|
if (CoreSites.getCurrentSiteId() === siteId) {
|
|
const key = handler[this.handlerNameProperty] || handler.name;
|
|
|
|
if (enabled) {
|
|
this.enabledHandlers[key] = handler;
|
|
} else {
|
|
delete this.enabledHandlers[key];
|
|
}
|
|
}
|
|
|
|
return;
|
|
}).finally(() => {
|
|
// Update finished, delete the promise.
|
|
delete this.updatePromises[siteId][handler.name];
|
|
});
|
|
|
|
return this.updatePromises[siteId][handler.name];
|
|
}
|
|
|
|
/**
|
|
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
|
*
|
|
* @param handler Handler to check.
|
|
* @param site Site to check.
|
|
* @returns Whether is enabled or disabled in site.
|
|
*/
|
|
protected isFeatureDisabled(handler: HandlerType, site: CoreSite): boolean {
|
|
return this.featurePrefix !== undefined && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
|
}
|
|
|
|
/**
|
|
* Update the handlers for the current site.
|
|
*
|
|
* @returns Resolved when done.
|
|
*/
|
|
async updateHandlers(): Promise<void> {
|
|
const promises: Promise<void>[] = [];
|
|
const now = Date.now();
|
|
|
|
this.logger.debug('Updating handlers for current site.');
|
|
|
|
this.lastUpdateHandlersStart = now;
|
|
|
|
// Loop over all the handlers.
|
|
for (const name in this.handlers) {
|
|
promises.push(this.updateHandler(this.handlers[name]));
|
|
}
|
|
|
|
try {
|
|
await Promise.all(promises);
|
|
} catch (e) {
|
|
// Never reject
|
|
}
|
|
|
|
// Verify that this call is the last one that was started.
|
|
if (this.isLastUpdateCall(now)) {
|
|
this.handlersInitialized = true;
|
|
this.handlersInitResolve();
|
|
|
|
this.updateData();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update handlers Data.
|
|
* Override this function to update handlers data.
|
|
*/
|
|
updateData(): void {
|
|
// To be overridden.
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Base interface for any delegate.
|
|
*/
|
|
export interface CoreDelegateHandler {
|
|
/**
|
|
* Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
|
|
* This name will be used to check if the feature is disabled.
|
|
*/
|
|
name: string;
|
|
|
|
/**
|
|
* Whether or not the handler is enabled on a site level.
|
|
*
|
|
* @returns Whether or not the handler is enabled on a site level.
|
|
*/
|
|
isEnabled(): Promise<boolean>;
|
|
}
|
|
|
|
/**
|
|
* Data returned by the delegate for each handler to be displayed.
|
|
*/
|
|
export interface CoreDelegateToDisplay {
|
|
/**
|
|
* Name of the handler.
|
|
*/
|
|
name?: string;
|
|
|
|
/**
|
|
* Priority of the handler.
|
|
*/
|
|
priority?: number;
|
|
}
|
|
|
|
/**
|
|
* Base interface for a core delegate needed to be displayed.
|
|
*/
|
|
export interface CoreDelegateDisplayHandler<HandlerData extends CoreDelegateToDisplay> extends CoreDelegateHandler {
|
|
/**
|
|
* The highest priority is displayed first.
|
|
*/
|
|
priority?: number;
|
|
|
|
/**
|
|
* Returns the data needed to render the handler.
|
|
*
|
|
* @returns Data.
|
|
*/
|
|
getDisplayData(): HandlerData;
|
|
}
|