mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
Add backend support for the webhooks bridge
This commit is contained in:
parent
235d8051fe
commit
93b532de44
88
src/bridges/WebhooksBridge.ts
Normal file
88
src/bridges/WebhooksBridge.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { LogService } from "matrix-js-snippets";
|
||||||
|
import * as request from "request";
|
||||||
|
import {
|
||||||
|
ListWebhooksResponse,
|
||||||
|
SuccessResponse,
|
||||||
|
WebhookConfiguration,
|
||||||
|
WebhookOptions,
|
||||||
|
WebhookResponse
|
||||||
|
} from "./models/webhooks";
|
||||||
|
import WebhookBridgeRecord from "../db/models/WebhookBridgeRecord";
|
||||||
|
|
||||||
|
export class WebhooksBridge {
|
||||||
|
|
||||||
|
constructor(private requestingUserId: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDefaultBridge(): Promise<WebhookBridgeRecord> {
|
||||||
|
const bridges = await WebhookBridgeRecord.findAll({where: {isEnabled: true}});
|
||||||
|
if (!bridges || bridges.length !== 1) {
|
||||||
|
throw new Error("No bridges or too many bridges found");
|
||||||
|
}
|
||||||
|
return bridges[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isBridgingEnabled(): Promise<boolean> {
|
||||||
|
const bridges = await WebhookBridgeRecord.findAll({where: {isEnabled: true}});
|
||||||
|
return !!bridges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getHooks(roomId: string): Promise<WebhookConfiguration[]> {
|
||||||
|
const bridge = await this.getDefaultBridge();
|
||||||
|
|
||||||
|
const response = await this.doProvisionRequest<ListWebhooksResponse>(bridge, "GET", `/api/v1/provision/${roomId}/hooks`);
|
||||||
|
if (!response.success) throw new Error("Failed to get webhooks");
|
||||||
|
return response.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createWebhook(roomId: string, options: WebhookOptions): Promise<WebhookConfiguration> {
|
||||||
|
const bridge = await this.getDefaultBridge();
|
||||||
|
return this.doProvisionRequest<WebhookResponse>(bridge, "PUT", `/api/v1/provision/${roomId}/hook`, null, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateWebhook(roomId: string, hookId: string, options: WebhookOptions): Promise<WebhookConfiguration> {
|
||||||
|
const bridge = await this.getDefaultBridge();
|
||||||
|
return this.doProvisionRequest<WebhookResponse>(bridge, "PUT", `/api/v1/provision/${roomId}/hook/${hookId}`, null, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteWebhook(roomId: string, hookId: string): Promise<any> {
|
||||||
|
const bridge = await this.getDefaultBridge();
|
||||||
|
return this.doProvisionRequest<SuccessResponse>(bridge, "DELETE", `/api/v1/provision/${roomId}/hook/${hookId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doProvisionRequest<T>(bridge: WebhookBridgeRecord, method: string, endpoint: string, qs?: any, body?: any): Promise<T> {
|
||||||
|
const provisionUrl = bridge.provisionUrl;
|
||||||
|
const apiUrl = provisionUrl.endsWith("/") ? provisionUrl.substring(0, provisionUrl.length - 1) : provisionUrl;
|
||||||
|
const url = apiUrl + (endpoint.startsWith("/") ? endpoint : "/" + endpoint);
|
||||||
|
LogService.info("WebhooksBridge", "Doing provision Webhooks Bridge request: " + url);
|
||||||
|
|
||||||
|
if (!qs) qs = {};
|
||||||
|
if (!qs["userId"]) qs["userId"] = this.requestingUserId;
|
||||||
|
qs["token"] = bridge.sharedSecret;
|
||||||
|
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
request({
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
qs: qs,
|
||||||
|
json: body,
|
||||||
|
}, (err, res, _body) => {
|
||||||
|
if (err) {
|
||||||
|
LogService.error("WebhooksBridge", "Error calling" + url);
|
||||||
|
LogService.error("WebhooksBridge", err);
|
||||||
|
reject(err);
|
||||||
|
} else if (!res) {
|
||||||
|
LogService.error("WebhooksBridge", "There is no response for " + url);
|
||||||
|
reject(new Error("No response provided - is the service online?"));
|
||||||
|
} else if (res.statusCode !== 200) {
|
||||||
|
LogService.error("WebhooksBridge", "Got status code " + res.statusCode + " when calling " + url);
|
||||||
|
LogService.error("WebhooksBridge", res.body);
|
||||||
|
reject(new Error("Request failed"));
|
||||||
|
} else {
|
||||||
|
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
||||||
|
resolve(res.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
23
src/bridges/models/webhooks.ts
Normal file
23
src/bridges/models/webhooks.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface WebhookConfiguration {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
userId: string;
|
||||||
|
roomId: string;
|
||||||
|
type: "incoming";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListWebhooksResponse extends SuccessResponse {
|
||||||
|
results: WebhookConfiguration[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookResponse extends WebhookConfiguration, SuccessResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookOptions {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuccessResponse {
|
||||||
|
success: boolean;
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { Bridge } from "../integrations/Bridge";
|
import { Bridge, WebhookBridgeConfiguration } from "../integrations/Bridge";
|
||||||
import BridgeRecord from "./models/BridgeRecord";
|
import BridgeRecord from "./models/BridgeRecord";
|
||||||
import { IrcBridge } from "../bridges/IrcBridge";
|
import { IrcBridge } from "../bridges/IrcBridge";
|
||||||
import { LogService } from "matrix-js-snippets";
|
import { LogService } from "matrix-js-snippets";
|
||||||
|
import { WebhooksBridge } from "../bridges/WebhooksBridge";
|
||||||
|
|
||||||
export class BridgeStore {
|
export class BridgeStore {
|
||||||
|
|
||||||
@ -44,6 +45,8 @@ export class BridgeStore {
|
|||||||
|
|
||||||
if (integrationType === "irc") {
|
if (integrationType === "irc") {
|
||||||
throw new Error("IRC Bridges should be modified with the dedicated API");
|
throw new Error("IRC Bridges should be modified with the dedicated API");
|
||||||
|
} else if (integrationType === "webhooks") {
|
||||||
|
throw new Error("Webhooks should be modified with the dedicated API");
|
||||||
} else throw new Error("Unsupported bridge");
|
} else throw new Error("Unsupported bridge");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +54,9 @@ export class BridgeStore {
|
|||||||
if (record.type === "irc") {
|
if (record.type === "irc") {
|
||||||
const irc = new IrcBridge(requestingUserId);
|
const irc = new IrcBridge(requestingUserId);
|
||||||
return irc.hasNetworks();
|
return irc.hasNetworks();
|
||||||
|
} else if (record.type === "webhooks") {
|
||||||
|
const webhooks = new WebhooksBridge(requestingUserId);
|
||||||
|
return webhooks.isBridgingEnabled();
|
||||||
} else return true;
|
} else return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +65,13 @@ export class BridgeStore {
|
|||||||
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
||||||
const irc = new IrcBridge(requestingUserId);
|
const irc = new IrcBridge(requestingUserId);
|
||||||
return irc.getRoomConfiguration(inRoomId);
|
return irc.getRoomConfiguration(inRoomId);
|
||||||
|
} else if (record.type === "webhooks") {
|
||||||
|
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
||||||
|
const webhooks = new WebhooksBridge(requestingUserId);
|
||||||
|
const hooks = await webhooks.getHooks(inRoomId);
|
||||||
|
return <WebhookBridgeConfiguration>{
|
||||||
|
webhooks: hooks,
|
||||||
|
};
|
||||||
} else return {};
|
} else return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import IrcBridgeNetwork from "./models/IrcBridgeNetwork";
|
|||||||
import StickerPack from "./models/StickerPack";
|
import StickerPack from "./models/StickerPack";
|
||||||
import Sticker from "./models/Sticker";
|
import Sticker from "./models/Sticker";
|
||||||
import UserStickerPack from "./models/UserStickerPack";
|
import UserStickerPack from "./models/UserStickerPack";
|
||||||
|
import WebhookBridgeRecord from "./models/WebhookBridgeRecord";
|
||||||
|
|
||||||
class _DimensionStore {
|
class _DimensionStore {
|
||||||
private sequelize: Sequelize;
|
private sequelize: Sequelize;
|
||||||
@ -53,6 +54,7 @@ class _DimensionStore {
|
|||||||
StickerPack,
|
StickerPack,
|
||||||
Sticker,
|
Sticker,
|
||||||
UserStickerPack,
|
UserStickerPack,
|
||||||
|
WebhookBridgeRecord,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
src/db/migrations/20181019205145-AddWebhooksBridge.ts
Normal file
23
src/db/migrations/20181019205145-AddWebhooksBridge.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
import { DataType } from "sequelize-typescript";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.createTable("dimension_webhook_bridges", {
|
||||||
|
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
"upstreamId": {
|
||||||
|
type: DataType.INTEGER, allowNull: true,
|
||||||
|
references: {model: "dimension_upstreams", key: "id"},
|
||||||
|
onUpdate: "cascade", onDelete: "cascade",
|
||||||
|
},
|
||||||
|
"provisionUrl": {type: DataType.STRING, allowNull: true},
|
||||||
|
"sharedSecret": {type: DataType.STRING, allowNull: true},
|
||||||
|
"isEnabled": {type: DataType.BOOLEAN, allowNull: false},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.dropTable("dimension_webhook_bridges"));
|
||||||
|
}
|
||||||
|
}
|
23
src/db/migrations/20181019205245-AddWebhookBridgeRecord.ts
Normal file
23
src/db/migrations/20181019205245-AddWebhookBridgeRecord.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.bulkInsert("dimension_bridges", [
|
||||||
|
{
|
||||||
|
type: "webhooks",
|
||||||
|
name: "Webhook Bridge",
|
||||||
|
avatarUrl: "/img/avatars/webhooks.png",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
description: "Slack-compatible webhooks for your room",
|
||||||
|
},
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.bulkDelete("dimension_bridges", {
|
||||||
|
type: "webhooks",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
30
src/db/models/WebhookBridgeRecord.ts
Normal file
30
src/db/models/WebhookBridgeRecord.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { AllowNull, AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript";
|
||||||
|
import Upstream from "./Upstream";
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: "dimension_webhook_bridges",
|
||||||
|
underscoredAll: false,
|
||||||
|
timestamps: false,
|
||||||
|
})
|
||||||
|
export default class WebhookBridgeRecord extends Model<WebhookBridgeRecord> {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@AllowNull
|
||||||
|
@Column
|
||||||
|
@ForeignKey(() => Upstream)
|
||||||
|
upstreamId?: number;
|
||||||
|
|
||||||
|
@AllowNull
|
||||||
|
@Column
|
||||||
|
provisionUrl?: string;
|
||||||
|
|
||||||
|
@AllowNull
|
||||||
|
@Column
|
||||||
|
sharedSecret?: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
isEnabled: boolean;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Integration } from "./Integration";
|
import { Integration } from "./Integration";
|
||||||
import BridgeRecord from "../db/models/BridgeRecord";
|
import BridgeRecord from "../db/models/BridgeRecord";
|
||||||
import { AvailableNetworks, LinkedChannels } from "../bridges/IrcBridge";
|
import { AvailableNetworks, LinkedChannels } from "../bridges/IrcBridge";
|
||||||
|
import { WebhookConfiguration } from "../bridges/models/webhooks";
|
||||||
|
|
||||||
export class Bridge extends Integration {
|
export class Bridge extends Integration {
|
||||||
constructor(bridge: BridgeRecord, public config: any) {
|
constructor(bridge: BridgeRecord, public config: any) {
|
||||||
@ -20,4 +21,8 @@ export class Bridge extends Integration {
|
|||||||
export interface IrcBridgeConfiguration {
|
export interface IrcBridgeConfiguration {
|
||||||
availableNetworks: AvailableNetworks;
|
availableNetworks: AvailableNetworks;
|
||||||
links: LinkedChannels;
|
links: LinkedChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookBridgeConfiguration {
|
||||||
|
webhooks: WebhookConfiguration[];
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user