Add a listeners config block (#102)

* Add support for a multi-resource listener style handler

* Update configs

* Speed up config check

* Speed up config check

* Update docs
This commit is contained in:
Will Hunt 2021-12-21 16:52:12 +00:00 committed by GitHub
parent 6d46492693
commit 060b73263f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 228 additions and 149 deletions

View File

@ -37,8 +37,8 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 16
- run: yarn --ignore-scripts && yarn build:app
- run: node lib/Config/Defaults.js --config | diff config.sample.yml -
- run: yarn --ignore-scripts
- run: yarn --silent ts-node src/Config/Defaults.ts --config | diff config.sample.yml -
metrics-docs:
runs-on: ubuntu-latest

1
changelog.d/102.feature Normal file
View File

@ -0,0 +1 @@
Allow running multiple resources on the same HTTP listener. See the new `listeners` config.

View File

@ -54,16 +54,9 @@ generic:
urlPrefix: https://example.com/mywebhookspath/
allowJsTransformationFunctions: false
userIdPrefix: webhooks_
webhook:
# HTTP webhook listener options
#
port: 9000
bindAddress: 0.0.0.0
provisioning:
# (Optional) Provisioning API for integration managers
#
bindAddress: 127.0.0.1
port: 9001
secret: "!secretToken"
passFile:
# A passkey used to encrypt tokens stored inside the bridge.
@ -79,8 +72,6 @@ metrics:
# (Optional) Prometheus metrics support
#
enabled: true
bindAddress: 127.0.0.1
port: 9002
queue:
# (Optional) Message queue / cache configuration options for large scale deployments
#
@ -91,4 +82,19 @@ logging:
# (Optional) Logging settings. You can have a severity debug,info,warn,error
#
level: info
listeners:
# (Optional) HTTP Listener configuration.
# Bind resource endpoints to ports and addresses.
# 'resources' may be any of webhooks, widgets, metrics, provisioning, appservice
#
- port: 9000
bindAddress: 0.0.0.0
resources:
- webhooks
- widgets
- port: 9001
bindAddress: 127.0.0.1
resources:
- metrics
- provisioning

View File

@ -58,17 +58,30 @@ Copy `registration.sample.yml` into `registration.yml` and fill in:
You will need to link the registration file to the homeserver. Consult your homeserver documentation
on how to add appservices. [Synapse documents the process here](https://matrix-org.github.io/synapse/latest/application_services.html)
### Webhooks configuration
### Listeners configuration
You will need to configure the webhooks listener at a minimum to make the bridge functional.
To do this, ensure that your configuration for `webhook` is correct:
You will need to configure some listeners to make the bridge functional.
```yaml
webhook:
port: 5061
host: 0.0.0.0
# (Optional) HTTP Listener configuration.
# Bind resource endpoints to ports and addresses.
# 'resources' may be any of webhooks, widgets, metrics, provisioning, appservice
#
- port: 9000
bindAddress: 0.0.0.0
resources:
- webhooks
- widgets
- port: 9001
bindAddress: 127.0.0.1
resources:
- metrics
- provisioning
```
At a minimum, you should bind the `webooks` resource to a port and address. You can have multiple resources on the same
port, or one on each.
You will also need to make this port accessible to the internet so services like GitHub can reach the bridge. It
is reccomended to factor hookshot into your load balancer configuration, but currrently this process is left as an
excercise to the user.

View File

@ -29,7 +29,7 @@
"lint": "yarn run lint:js && yarn run lint:rs",
"lint:js": "eslint -c .eslintrc.js src/**/*.ts",
"lint:rs": "cargo fmt --all -- --check",
"generate-default-config": "node lib/Config/Defaults.js --config > config.sample.yml"
"generate-default-config": "ts-node src/Config/Defaults.ts --config > config.sample.yml"
},
"dependencies": {
"@alloc/quick-lru": "^5.2.0",

View File

@ -5,6 +5,7 @@ import { BridgeConfig, parseRegistrationFile } from "../Config/Config";
import { Webhooks } from "../Webhooks";
import { MatrixSender } from "../MatrixSender";
import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher";
import { ListenerService } from "../ListenerService";
LogWrapper.configureLogging("debug");
const log = new LogWrapper("App");
@ -14,24 +15,27 @@ async function start() {
const registrationFile = process.argv[3] || "./registration.yml";
const config = await BridgeConfig.parseConfig(configFile, process.env);
const registration = await parseRegistrationFile(registrationFile);
const listener = new ListenerService(config.listeners);
LogWrapper.configureLogging(config.logging.level);
if (config.queue.monolithic) {
const webhookHandler = new Webhooks(config);
webhookHandler.listen();
listener.bindResource('webhooks', webhookHandler.expressRouter);
const matrixSender = new MatrixSender(config, registration);
matrixSender.listen();
const userNotificationWatcher = new UserNotificationWatcher(config);
userNotificationWatcher.start();
}
const bridgeApp = new Bridge(config, registration);
const bridgeApp = new Bridge(config, registration, listener);
process.once("SIGTERM", () => {
log.error("Got SIGTERM");
listener.stop();
bridgeApp.stop();
});
await bridgeApp.start();
listener.start();
}
start().catch((ex) => {

View File

@ -3,6 +3,7 @@ import { Webhooks } from "../Webhooks";
import LogWrapper from "../LogWrapper";
import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher";
import Metrics from "../Metrics";
import { ListenerService } from "../ListenerService";
const log = new LogWrapper("App");
@ -11,22 +12,24 @@ async function start() {
const configFile = process.argv[2] || "./config.yml";
const config = await BridgeConfig.parseConfig(configFile, process.env);
LogWrapper.configureLogging(config.logging.level);
const listener = new ListenerService(config.listeners);
if (config.metrics) {
if (!config.metrics.port) {
log.warn(`Not running metrics for service, no port specified`);
} else {
Metrics.start(config.metrics);
listener.bindResource('metrics', Metrics.expressRouter);
}
}
const webhookHandler = new Webhooks(config);
webhookHandler.listen();
listener.bindResource('webhooks', webhookHandler.expressRouter);
const userWatcher = new UserNotificationWatcher(config);
userWatcher.start();
listener.start();
process.once("SIGTERM", () => {
log.error("Got SIGTERM");
webhookHandler.stop();
listener.stop();
userWatcher.stop();
Metrics.stop();
});
}

View File

@ -2,6 +2,7 @@ import { BridgeConfig, parseRegistrationFile } from "../Config/Config";
import { MatrixSender } from "../MatrixSender";
import LogWrapper from "../LogWrapper";
import Metrics from "../Metrics";
import { ListenerService } from "../ListenerService";
const log = new LogWrapper("App");
@ -12,19 +13,21 @@ async function start() {
const config = await BridgeConfig.parseConfig(configFile, process.env);
const registration = await parseRegistrationFile(registrationFile);
LogWrapper.configureLogging(config.logging.level);
const listener = new ListenerService(config.listeners);
const sender = new MatrixSender(config, registration);
if (config.metrics) {
if (!config.metrics.port) {
log.warn(`Not running metrics for service, no port specified`);
} else {
Metrics.start(config.metrics);
listener.bindResource('metrics', Metrics.expressRouter);
}
}
sender.listen();
listener.start();
process.once("SIGTERM", () => {
log.error("Got SIGTERM");
sender.stop();
Metrics.stop();
listener.stop();
});
}

View File

@ -35,6 +35,7 @@ import { OAuthRequest } from "./WebhookTypes";
import { promises as fs } from "fs";
import { SetupConnection } from "./Connections/SetupConnection";
import Metrics from "./Metrics";
import { ListenerService } from "./ListenerService";
const log = new LogWrapper("Bridge");
export function getAppservice(config: BridgeConfig, registration: IAppserviceRegistration, storage: IAppserviceStorageProvider) {
@ -77,7 +78,7 @@ export class Bridge {
private ready = false;
constructor(private config: BridgeConfig, private registration: IAppserviceRegistration) {
constructor(private config: BridgeConfig, private registration: IAppserviceRegistration, private readonly listener: ListenerService) {
if (this.config.queue.host && this.config.queue.port) {
log.info(`Initialising Redis storage (on ${this.config.queue.host}:${this.config.queue.port})`);
this.storage = new RedisStorageProvider(this.config.queue.host, this.config.queue.port);
@ -95,10 +96,7 @@ export class Bridge {
public stop() {
this.as.stop();
Metrics.stop();
if (this.queue.stop) this.queue.stop();
if (this.widgetApi) this.widgetApi.stop();
if (this.provisioningApi) this.provisioningApi.stop();
}
public async start() {
@ -578,13 +576,13 @@ export class Bridge {
}
if (this.config.widgets) {
await this.widgetApi.start(this.config.widgets.port);
this.listener.bindResource('widgets', this.widgetApi.expressRouter);
}
if (this.provisioningApi) {
await this.provisioningApi.listen();
this.listener.bindResource('provisioning', this.provisioningApi.expressRouter);
}
if (this.config.metrics?.enabled) {
Metrics.start(this.config.metrics, this.as);
this.listener.bindResource('metrics', Metrics.expressRouter);
}
await this.as.begin();
log.info("Started bridge");

View File

@ -3,6 +3,7 @@ import { promises as fs } from "fs";
import { IAppserviceRegistration } from "matrix-bot-sdk";
import * as assert from "assert";
import { configKey } from "./Decorators";
import { BridgeConfigListener, ResourceTypeArray } from "../ListenerService";
import { GitHubRepoConnectionOptions } from "../Connections/GithubRepo";
interface BridgeConfigGitHubYAML {
@ -92,11 +93,12 @@ export interface BridgeGenericWebhooksConfig {
}
interface BridgeWidgetConfig {
port: number;
port?: number;
addToAdminRooms: boolean;
publicUrl: string;
}
interface BridgeConfigBridge {
domain: string;
url: string;
@ -111,8 +113,8 @@ interface BridgeConfigBridge {
}
interface BridgeConfigWebhook {
port: number;
bindAddress: string;
port?: number;
bindAddress?: string;
}
interface BridgeConfigQueue {
@ -132,7 +134,7 @@ interface BridgeConfigBot {
export interface BridgeConfigProvisioning {
bindAddress?: string;
port: number;
port?: number;
secret: string;
}
@ -142,7 +144,6 @@ export interface BridgeConfigMetrics {
port?: number;
}
interface BridgeConfigRoot {
bot?: BridgeConfigBot;
bridge: BridgeConfigBridge;
@ -154,16 +155,15 @@ interface BridgeConfigRoot {
logging: BridgeConfigLogging;
passFile: string;
queue: BridgeConfigQueue;
webhook: BridgeConfigWebhook;
webhook?: BridgeConfigWebhook;
widgets?: BridgeWidgetConfig;
metrics?: BridgeConfigMetrics;
listeners?: BridgeConfigListener[];
}
export class BridgeConfig {
@configKey("Basic homeserver configuration")
public readonly bridge: BridgeConfigBridge;
@configKey("HTTP webhook listener options")
public readonly webhook: BridgeConfigWebhook;
@configKey("Message queue / cache configuration options for large scale deployments", true)
public readonly queue: BridgeConfigQueue;
@configKey("Logging settings. You can have a severity debug,info,warn,error", true)
@ -188,6 +188,11 @@ export class BridgeConfig {
@configKey("Prometheus metrics support", true)
public readonly metrics?: BridgeConfigMetrics;
@configKey(`HTTP Listener configuration.
Bind resource endpoints to ports and addresses.
'resources' may be any of ${ResourceTypeArray.join(', ')}`, true)
public readonly listeners: BridgeConfigListener[];
constructor(configData: BridgeConfigRoot, env: {[key: string]: string|undefined}) {
this.bridge = configData.bridge;
assert.ok(this.bridge);
@ -201,12 +206,10 @@ export class BridgeConfig {
this.gitlab = configData.gitlab;
this.jira = configData.jira;
this.generic = configData.generic;
this.webhook = configData.webhook;
this.provisioning = configData.provisioning;
this.passFile = configData.passFile;
this.bot = configData.bot;
this.metrics = configData.metrics;
assert.ok(this.webhook);
this.queue = configData.queue || {
monolithic: true,
};
@ -220,6 +223,41 @@ export class BridgeConfig {
this.queue.port = env.CFG_QUEUE_POST ? parseInt(env.CFG_QUEUE_POST, 10) : undefined;
}
// Listeners is a bit special
this.listeners = configData.listeners || [];
// For legacy reasons, copy across the per-service listener config into the listeners array.
if (configData.webhook?.port) {
this.listeners.push({
resources: ['webhooks'],
port: configData.webhook.port,
bindAddress: configData.webhook.bindAddress,
})
}
if (this.provisioning?.port) {
this.listeners.push({
resources: ['provisioning'],
port: this.provisioning.port,
bindAddress: this.provisioning.bindAddress,
})
}
if (this.metrics?.port) {
this.listeners.push({
resources: ['metrics'],
port: this.metrics.port,
bindAddress: this.metrics.bindAddress,
})
}
if (this.widgets?.port) {
this.listeners.push({
resources: ['widgets'],
port: this.widgets.port,
})
}
}
static async parseConfig(filename: string, env: {[key: string]: string|undefined}) {

View File

@ -21,12 +21,7 @@ export const DefaultConfig = new BridgeConfig({
level: "info",
},
passFile: "passkey.pem",
webhook: {
port: 9000,
bindAddress: "0.0.0.0"
},
widgets: {
port: 5000,
publicUrl: "https://example.com/bridge_widget/",
addToAdminRooms: true,
},
@ -78,15 +73,23 @@ export const DefaultConfig = new BridgeConfig({
userIdPrefix: "webhooks_",
},
provisioning: {
bindAddress: "127.0.0.1",
port: 9001,
secret: "!secretToken"
},
metrics: {
enabled: true,
bindAddress: "127.0.0.1",
port: 9002,
},
listeners: [
{
port: 9000,
bindAddress: '0.0.0.0',
resources: ['webhooks', 'widgets'],
},
{
port: 9001,
bindAddress: '127.0.0.1',
resources: ['metrics', 'provisioning'],
}
]
}, {});
function renderSection(doc: YAML.Document, obj: Record<string, unknown>, parentNode?: YAMLSeq) {

71
src/ListenerService.ts Normal file
View File

@ -0,0 +1,71 @@
import { Server } from "http";
import { Application, default as expressApp, Router } from "express";
import LogWrapper from "./LogWrapper";
// Appserices can't be handled yet because the bot-sdk maintains control of it.
export type ResourceName = "webhooks"|"widgets"|"metrics"|"provisioning"|"appservice";
export const ResourceTypeArray: ResourceName[] = ["webhooks","widgets","metrics","provisioning","appservice"];
export interface BridgeConfigListener {
bindAddress?: string;
port: number;
resources: Array<ResourceName>;
}
const log = new LogWrapper("ListenerService");
export class ListenerService {
private readonly listeners: {
server?: Server,
app: Application,
config: BridgeConfigListener,
resourcesBound: boolean,
}[] = [];
constructor(config: BridgeConfigListener[]) {
if (config.length < 1) {
throw Error('No listeners configured');
}
for (const listenerConfig of config) {
const app = expressApp();
this.listeners.push({
config: listenerConfig,
app,
resourcesBound: false,
});
}
}
public bindResource(resourceName: ResourceName, router: Router) {
for (const listener of this.listeners.filter((l) => l.config.resources.includes(resourceName))) {
log.info(`Registering ${listener.config.bindAddress || "127.0.0.1"}:${listener.config.port} for ${resourceName}`);
listener.app.use(router);
listener.resourcesBound = true;
}
}
public start() {
for (const listener of this.listeners) {
if (listener.server) {
throw Error('Cannot run start() twice');
}
if (!listener.resourcesBound) {
continue;
}
const addr = listener.config.bindAddress || "127.0.0.1";
listener.server = listener.app.listen(listener.config.port, addr);
log.info(`Listening on ${addr}:${listener.config.port} for ${listener.config.resources.join(', ')}`)
}
}
public async stop() {
const promises = [];
log.info(`Stopping all listeners`);
for (const listener of this.listeners) {
if (listener.server) {
promises.push(new Promise<void>((res, rej) => listener.server?.close((e) => e ? rej(e) : res())));
}
}
await Promise.all(promises);
}
}

View File

@ -1,13 +1,11 @@
import { Appservice, FunctionCallContext, METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL, METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL } from "matrix-bot-sdk";
import { collectDefaultMetrics, Counter, Gauge, register, Registry } from "prom-client";
import { BridgeConfigMetrics } from "./Config/Config";
import { Response, default as expressApp } from "express";
import { Response, Router } from "express";
import LogWrapper from "./LogWrapper";
import { Server } from "http";
const log = new LogWrapper("Metrics");
export class Metrics {
private httpServer?: Server;
public readonly expressRouter = Router();
public readonly webhooksHttpRequest = new Counter({ name: "hookshot_webhooks_http_request", help: "Number of requests made to the hookshot webhooks handler", labelNames: ["path", "method"], registers: [this.registry]});
public readonly provisioningHttpRequest = new Counter({ name: "hookshot_provisioning_http_request", help: "Number of requests made to the hookshot webhooks handler", labelNames: ["path", "method"], registers: [this.registry]});
@ -24,6 +22,7 @@ export class Metrics {
public readonly matrixAppserviceEvents = new Counter({ name: "matrix_appservice_events", help: "The number of events sent over the AS API", labelNames: [], registers: [this.registry]});
constructor(private registry: Registry = register) {
this.expressRouter.get('/metrics', this.metricsFunc.bind(this));
collectDefaultMetrics({
register: this.registry
})
@ -79,26 +78,6 @@ export class Metrics {
res.status(500).send('Could not fetch metrics due to an error');
});
}
public start(config: BridgeConfigMetrics, as?: Appservice) {
if (!config.port) {
if (!as) {
throw Error("No metric port defined in config, and service doesn't run a appservice");
}
as.expressAppInstance.get('/metrics', this.metricsFunc.bind(this));
return;
}
const app = expressApp();
app.get('/metrics', this.metricsFunc.bind(this));
this.httpServer = app.listen(config.port, config.bindAddress || "127.0.0.1");
}
public async stop() {
if (!this.httpServer) {
return;
}
return new Promise<void>((res, rej) => this.httpServer?.close(err => err ? rej(err) : res()));
}
}
const singleton = new Metrics();

View File

@ -1,5 +1,5 @@
import { BridgeConfig } from "./Config/Config";
import { Application, default as express, Request, Response } from "express";
import { Router, default as express, Request, Response } from "express";
import { EventEmitter } from "events";
import { MessageQueue, createMessageQueue } from "./MessageQueue";
import LogWrapper from "./LogWrapper";
@ -37,14 +37,12 @@ export interface NotificationsDisableEvent {
}
export class Webhooks extends EventEmitter {
private expressApp: Application;
public readonly expressRouter = Router();
private queue: MessageQueue;
private server?: Server;
private ghWebhooks?: OctokitWebhooks;
constructor(private config: BridgeConfig) {
super();
this.expressApp = express();
this.expressApp.use((req, _res, next) => {
this.expressRouter.use((req, _res, next) => {
Metrics.webhooksHttpRequest.inc({path: req.path, method: req.method});
next();
});
@ -56,40 +54,28 @@ export class Webhooks extends EventEmitter {
}
// TODO: Move these
this.expressApp.get("/oauth", this.onGitHubGetOauth.bind(this));
this.expressApp.all(
this.expressRouter.get("/oauth", this.onGitHubGetOauth.bind(this));
this.expressRouter.all(
'/:hookId',
express.json({ type: ['application/json', 'application/x-www-form-urlencoded'] }),
this.onGenericPayload.bind(this),
);
this.expressApp.use(express.json({
this.expressRouter.use(express.json({
verify: this.verifyRequest.bind(this),
}));
this.expressApp.post("/", this.onPayload.bind(this));
this.expressRouter.post("/", this.onPayload.bind(this));
this.queue = createMessageQueue(config);
if (this.config.jira) {
this.expressApp.use("/jira", new JiraWebhooksRouter(this.config.jira, this.queue).getRouter());
this.expressRouter.use("/jira", new JiraWebhooksRouter(this.config.jira, this.queue).getRouter());
}
this.queue = createMessageQueue(config);
}
public listen() {
const bindAddr = this.config.webhook.bindAddress || "0.0.0.0";
this.server = this.expressApp.listen(
this.config.webhook.port,
bindAddr,
);
log.info(`Listening on http://${bindAddr}:${this.config.webhook.port}`);
}
public stop() {
if (this.queue.stop) {
this.queue.stop();
}
if (this.server) {
this.server.close();
}
}

View File

@ -1,4 +1,4 @@
import { Application, default as express, Request, Response } from "express";
import express, { Router, Request, Response } from "express";
import cors from "cors";
import { AdminRoom } from "../AdminRoom";
import LogWrapper from "../LogWrapper";
@ -7,27 +7,19 @@ import { Server } from "http";
const log = new LogWrapper("BridgeWidgetApi");
export class BridgeWidgetApi {
private app: Application;
public readonly expressRouter: Router;
private server?: Server;
constructor(private adminRooms: Map<string, AdminRoom>) {
this.app = express();
this.app.use((req, _res, next) => {
this.expressRouter = Router();
this.expressRouter.use((req, _res, next) => {
log.info(`${req.method} ${req.path} ${req.ip || ''} ${req.headers["user-agent"] || ''}`);
next();
});
this.app.use('/', express.static('public'));
this.app.use(cors());
this.app.get('/widgetapi/:roomId/verify', this.getVerifyToken.bind(this));
this.app.get('/widgetapi/:roomId', this.getRoomState.bind(this));
this.app.get('/health', this.getHealth.bind(this));
}
public start(port = 5000) {
log.info(`Widget API listening on port ${port}`)
this.server = this.app.listen(port);
}
public stop() {
if (this.server) this.server.close();
this.expressRouter.use('/', express.static('public'));
this.expressRouter.use(cors());
this.expressRouter.get('/widgetapi/:roomId/verify', this.getVerifyToken.bind(this));
this.expressRouter.get('/widgetapi/:roomId', this.getRoomState.bind(this));
this.expressRouter.get('/health', this.getHealth.bind(this));
}
private async getRoomFromRequest(req: Request): Promise<AdminRoom|{error: string, statusCode: number}> {

View File

@ -1,8 +1,7 @@
import { BridgeConfigProvisioning } from "../Config/Config";
import { Application, default as express, NextFunction, Request, Response, Router } from "express";
import { Router, default as express, NextFunction, Request, Response } from "express";
import { ConnectionManager } from "../ConnectionManager";
import LogWrapper from "../LogWrapper";
import { Server } from "http";
import { ApiError, ErrCode, GetConnectionsResponseItem, GetConnectionTypeResponseItem } from "./api";
import { Intent, MembershipEventContent, PowerLevelsEventContent } from "matrix-bot-sdk";
import Metrics from "../Metrics";
@ -15,8 +14,7 @@ const USER_ID_VALIDATOR = /@.+:.+/;
export class Provisioner {
private expressApp: Application;
private server?: Server;
public readonly expressRouter: Router = Router();
constructor(
private readonly config: BridgeConfigProvisioning,
private readonly connMan: ConnectionManager,
@ -28,54 +26,53 @@ export class Provisioner {
if (!this.config.port) {
throw Error('Missing port in provisioning config');
}
this.expressApp = express();
this.expressApp.use((req, _res, next) => {
this.expressRouter.use((req, _res, next) => {
Metrics.provisioningHttpRequest.inc({path: req.path, method: req.method});
next();
});
this.expressApp.get("/v1/health", this.getHealth);
this.expressApp.use(this.checkAuth.bind(this));
this.expressApp.use(express.json());
this.expressRouter.get("/v1/health", this.getHealth);
this.expressRouter.use(this.checkAuth.bind(this));
this.expressRouter.use(express.json());
// Room Routes
this.expressApp.get(
this.expressRouter.get(
"/v1/connectiontypes",
this.getConnectionTypes.bind(this),
);
this.expressApp.use(this.checkUserId.bind(this));
this.expressRouter.use(this.checkUserId.bind(this));
additionalRoutes.forEach(route => {
this.expressApp.use(route.route, route.router);
this.expressRouter.use(route.route, route.router);
});
this.expressApp.get<{roomId: string}, unknown, unknown, {userId: string}>(
this.expressRouter.get<{roomId: string}, unknown, unknown, {userId: string}>(
"/v1/:roomId/connections",
this.checkRoomId.bind(this),
(...args) => this.checkUserPermission("read", ...args),
this.getConnections.bind(this),
);
this.expressApp.get<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
this.expressRouter.get<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
"/v1/:roomId/connections/:connectionId",
this.checkRoomId.bind(this),
(...args) => this.checkUserPermission("read", ...args),
this.getConnection.bind(this),
);
this.expressApp.put<{roomId: string, type: string}, unknown, Record<string, unknown>, {userId: string}>(
this.expressRouter.put<{roomId: string, type: string}, unknown, Record<string, unknown>, {userId: string}>(
"/v1/:roomId/connections/:type",
this.checkRoomId.bind(this),
(...args) => this.checkUserPermission("write", ...args),
this.putConnection.bind(this),
);
this.expressApp.patch<{roomId: string, connectionId: string}, unknown, Record<string, unknown>, {userId: string}>(
this.expressRouter.patch<{roomId: string, connectionId: string}, unknown, Record<string, unknown>, {userId: string}>(
"/v1/:roomId/connections/:connectionId",
this.checkRoomId.bind(this),
(...args) => this.checkUserPermission("write", ...args),
this.patchConnection.bind(this),
);
this.expressApp.delete<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
this.expressRouter.delete<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
"/v1/:roomId/connections/:connectionId",
this.checkRoomId.bind(this),
(...args) => this.checkUserPermission("write", ...args),
this.deleteConnection.bind(this),
);
this.expressApp.use(this.onError);
this.expressRouter.use(this.onError);
}
private checkAuth(req: Request, _res: Response, next: NextFunction) {
@ -247,19 +244,4 @@ export class Provisioner {
return next(ex);
}
}
public listen() {
const bindAddr = this.config.bindAddress || "0.0.0.0";
this.server = this.expressApp.listen(
this.config.port,
bindAddr,
);
log.info(`Listening on http://${bindAddr}:${this.config.port}`);
}
public stop() {
if (this.server) {
this.server.close();
}
}
}