diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 920b756f..fb87cfe7 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -302,12 +302,7 @@ export class Appservice extends EventEmitter { this.app.post("/_matrix/app/v1/unstable/org.matrix.msc3984/keys/query", this.onKeysQuery.bind(this)); this.app.post("/unstable/org.matrix.msc3984/keys/query", this.onKeysQuery.bind(this)); - // Everything else should 404 - // Technically, according to https://spec.matrix.org/v1.6/application-service-api/#unknown-routes we should - // be returning 405 for *known* endpoints with the wrong method. - this.app.all("*", (req: express.Request, res: express.Response) => { - res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); - }); + // We register the 404 handler in the `begin()` function to allow consumers to add their own endpoints. if (!this.registration.namespaces || !this.registration.namespaces.users || this.registration.namespaces.users.length === 0) { throw new Error("No user namespaces in registration"); @@ -381,6 +376,13 @@ export class Appservice extends EventEmitter { */ public begin(): Promise { return new Promise((resolve, reject) => { + // Per constructor, all other endpoints should 404. + // Technically, according to https://spec.matrix.org/v1.6/application-service-api/#unknown-routes we should + // be returning 405 for *known* endpoints with the wrong method. + this.app.all("*", (req: express.Request, res: express.Response) => { + res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); + }); + this.appServer = this.app.listen(this.options.port, this.options.bindAddress, () => resolve()); }).then(async () => { if (this.options.intentOptions?.encryption) { diff --git a/test/appservice/AppserviceTest.ts b/test/appservice/AppserviceTest.ts index b6d1506c..2f3e7c94 100644 --- a/test/appservice/AppserviceTest.ts +++ b/test/appservice/AppserviceTest.ts @@ -2039,6 +2039,44 @@ describe('Appservice', () => { } }); + it('should allow custom endpoints to be added to the express instance', async () => { + const port = await getPort(); + const hsToken = "s3cret_token"; + const appservice = new Appservice({ + port: port, + bindAddress: '', + homeserverName: 'example.org', + homeserverUrl: 'https://localhost', + registration: { + as_token: "", + hs_token: hsToken, + sender_localpart: "_bot_", + namespaces: { + users: [{ exclusive: true, regex: "@_prefix_.*:.+" }], + rooms: [], + aliases: [], + }, + }, + }); + appservice.botIntent.ensureRegistered = () => { + return null; + }; + + appservice.expressAppInstance.get("/test", (_, res) => res.sendStatus(200)); + + await appservice.begin(); + + try { + const res = await requestPromise({ + uri: `http://localhost:${port}/test`, + method: "GET", + }); + expect(res).toEqual("OK"); + } finally { + appservice.stop(); + } + }); + // TODO: Populate once intent tests are stable it.skip('should not try decryption if crypto is not possible', async () => {