Skip to content

Commit

Permalink
Add messenger extensibility to extensions #753
Browse files Browse the repository at this point in the history
  • Loading branch information
bpatrik committed Nov 19, 2023
1 parent 50b8f7a commit ebb9886
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 9 deletions.
8 changes: 6 additions & 2 deletions src/backend/model/extension/ExpressRouterWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,18 @@ export class ExpressRouteWrapper implements IExtensionRESTRoute {
},
RenderingMWs.renderResult
])));
this.extLogger.silly(`Listening on ${this.func} ${ExtensionManager.EXTENSION_API_PATH}${fullPaths}`);
const p = ExtensionManager.EXTENSION_API_PATH + fullPaths;
this.extLogger.silly(`Listening on ${this.func} ${p}`);
return p;
}

public rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise<void>) {
const fullPaths = paths.map(p => (Utils.concatUrls('/' + this.name + '/' + p)));
this.router[this.func](fullPaths,
...this.getAuthMWs(minRole),
mw);
this.extLogger.silly(`Listening on ${this.func} ${ExtensionManager.EXTENSION_API_PATH}${fullPaths}`);
const p = ExtensionManager.EXTENSION_API_PATH + fullPaths;
this.extLogger.silly(`Listening on ${this.func} ${p}`);
return p;
}
}
9 changes: 5 additions & 4 deletions src/backend/model/extension/ExtensionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as fs from 'fs';
import * as path from 'path';
import {IObjectManager} from '../database/IObjectManager';
import {Logger} from '../../Logger';
import {IExtensionEvents, IExtensionObject, IServerExtension} from './IExtension';
import {IExtensionEvents, IExtensionObject} from './IExtension';
import {Server} from '../../server';
import {ExtensionEvent} from './ExtensionEvent';
import * as express from 'express';
Expand Down Expand Up @@ -108,8 +108,8 @@ export class ExtensionManager implements IObjectManager {

if (fs.existsSync(packageJsonPath)) {
Logger.silly(LOG_TAG, `Running: "npm install --omit=dev" in ${extPath}`);
await exec('npm install --omit=dev' ,{
cwd:extPath
await exec('npm install --omit=dev', {
cwd: extPath
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(packageJsonPath);
Expand Down Expand Up @@ -138,8 +138,9 @@ export class ExtensionManager implements IObjectManager {
const ext = require(serverExt);
if (typeof ext?.cleanUp === 'function') {
Logger.debug(LOG_TAG, 'Running Init on extension:' + extObj.extensionName);
await ext?.cleanUp(ext);
await ext?.cleanUp(extObj);
}
extObj.messengers.cleanUp();
}
}

Expand Down
31 changes: 31 additions & 0 deletions src/backend/model/extension/ExtensionMessengerHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {IExtensionMessengers} from './IExtension';
import {DynamicConfig} from '../../../common/entities/DynamicConfig';
import {MediaDTOWithThPath, Messenger} from '../messenger/Messenger';
import {ExtensionMessenger} from '../messenger/ExtensionMessenger';
import {MessengerRepository} from '../messenger/MessengerRepository';
import {ILogger} from '../../Logger';

export class ExtensionMessengerHandler implements IExtensionMessengers {

messengers: Messenger[] = [];


constructor(private readonly extLogger: ILogger) {
}


addMessenger<C extends Record<string, unknown>>(name: string, config: DynamicConfig[], callbacks: {
sendMedia: (config: C, media: MediaDTOWithThPath[]) => Promise<void>
}): void {
this.extLogger.silly('Adding new Messenger:', name);
const em = new ExtensionMessenger(name, config, callbacks);
this.messengers.push(em);
MessengerRepository.Instance.register(em);
}

cleanUp() {
this.extLogger.silly('Removing Messenger');
this.messengers.forEach(m => MessengerRepository.Instance.remove(m));
}

}
3 changes: 3 additions & 0 deletions src/backend/model/extension/ExtensionObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ProjectPath} from '../../ProjectPath';
import {ExpressRouterWrapper} from './ExpressRouterWrapper';
import {createLoggerWrapper} from '../../Logger';
import * as express from 'express';
import {ExtensionMessengerHandler} from './ExtensionMessengerHandler';

export class ExtensionObject<C> implements IExtensionObject<C> {

Expand All @@ -16,6 +17,7 @@ export class ExtensionObject<C> implements IExtensionObject<C> {
public readonly Logger;
public readonly events;
public readonly RESTApi;
public readonly messengers;

constructor(public readonly extensionId: string,
public readonly extensionName: string,
Expand All @@ -30,6 +32,7 @@ export class ExtensionObject<C> implements IExtensionObject<C> {
this.Logger = logger;
this.events = events;
this.RESTApi = new ExpressRouterWrapper(extensionRouter, extensionId, logger);
this.messengers = new ExtensionMessengerHandler(logger);
}

}
29 changes: 26 additions & 3 deletions src/backend/model/extension/IExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {ILogger} from '../../Logger';
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
import {ParamsDictionary} from 'express-serve-static-core';
import {Connection} from 'typeorm';
import {DynamicConfig} from '../../../common/entities/DynamicConfig';
import {MediaDTOWithThPath} from '../messenger/Messenger';


export type IExtensionBeforeEventHandler<I, O> = (input: { inputs: I }, event: { stopPropagation: boolean }) => Promise<{ inputs: I } | O>;
Expand Down Expand Up @@ -69,16 +71,18 @@ export interface IExtensionRESTRoute {
* @param paths RESTapi path, relative to the extension base endpoint
* @param minRole set to null to omit auer check (ie make the endpoint public)
* @param cb function callback
* @return newly added REST api path
*/
jsonResponse(paths: string[], minRole: UserRoles, cb: (params?: ParamsDictionary, body?: any, user?: UserDTO) => Promise<unknown> | unknown): void;
jsonResponse(paths: string[], minRole: UserRoles, cb: (params?: ParamsDictionary, body?: any, user?: UserDTO) => Promise<unknown> | unknown): string;

/**
* Exposes a standard expressjs middleware
* @param paths RESTapi path, relative to the extension base endpoint
* @param minRole set to null to omit auer check (ie make the endpoint public)
* @param mw expressjs middleware
* @return newly added REST api path
*/
rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise<void>): void;
rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise<void>): string;
}

export interface IExtensionRESTApi {
Expand Down Expand Up @@ -116,9 +120,21 @@ export interface IExtensionConfig<C> {
getConfig(): C;
}

export interface IExtensionMessengers {
/**
* Adds a new messenger that the user can select e.g.: for sending top pick photos
* @param name Name of the messenger (also used as id)
* @param config config metadata for this messenger
* @param callbacks messenger logic
*/
addMessenger<C extends Record<string, unknown> = Record<string, unknown>>(name: string, config: DynamicConfig[], callbacks: {
sendMedia: (config: C, media: MediaDTOWithThPath[]) => Promise<void>
}): void;
}

export interface IExtensionObject<C> {
/**
* ID of the extension that is internally used. By default the name and ID matches if there is no collision.
* ID of the extension that is internally used. By default, the name and ID matches if there is no collision.
*/
extensionId: string,

Expand Down Expand Up @@ -159,6 +175,13 @@ export interface IExtensionObject<C> {
* Use this to define REST calls related to the extension
*/
RESTApi: IExtensionRESTApi;

/**
* Object to manipulate messengers.
* Messengers are used to send messages (like emails) from the app.
* One type of message is a list of selected photos.
*/
messengers: IExtensionMessengers;
}


Expand Down
15 changes: 15 additions & 0 deletions src/backend/model/messenger/ExtensionMessenger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {MediaDTOWithThPath, Messenger} from './Messenger';
import {DynamicConfig} from '../../../common/entities/DynamicConfig';

export class ExtensionMessenger<C extends Record<string, unknown> = Record<string, unknown>> extends Messenger<C> {

constructor(public readonly Name: string,
public readonly ConfigTemplate: DynamicConfig[],
private readonly callbacks: { sendMedia: (config: C, media: MediaDTOWithThPath[]) => Promise<void> }) {
super();
}

protected sendMedia(config: C, media: MediaDTOWithThPath[]): Promise<void> {
return this.callbacks.sendMedia(config, media);
}
}
7 changes: 7 additions & 0 deletions src/backend/model/messenger/MessengerRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export class MessengerRepository {
return Object.values(this.messengers);
}

remove(m: Messenger<Record<string, unknown>>): void {
if (!this.messengers[m.Name]) {
throw new Error('Messenger does not exist:' + m.Name);
}
delete this.messengers[m.Name];
}

register(msgr: Messenger): void {
if (typeof this.messengers[msgr.Name] !== 'undefined') {
throw new Error('Messenger already exist:' + msgr.Name);
Expand Down

0 comments on commit ebb9886

Please sign in to comment.