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 { IrcBridge } from "../bridges/IrcBridge";
|
||||
import { LogService } from "matrix-js-snippets";
|
||||
import { WebhooksBridge } from "../bridges/WebhooksBridge";
|
||||
|
||||
export class BridgeStore {
|
||||
|
||||
@ -44,6 +45,8 @@ export class BridgeStore {
|
||||
|
||||
if (integrationType === "irc") {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -51,6 +54,9 @@ export class BridgeStore {
|
||||
if (record.type === "irc") {
|
||||
const irc = new IrcBridge(requestingUserId);
|
||||
return irc.hasNetworks();
|
||||
} else if (record.type === "webhooks") {
|
||||
const webhooks = new WebhooksBridge(requestingUserId);
|
||||
return webhooks.isBridgingEnabled();
|
||||
} else return true;
|
||||
}
|
||||
|
||||
@ -59,6 +65,13 @@ export class BridgeStore {
|
||||
if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs
|
||||
const irc = new IrcBridge(requestingUserId);
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import IrcBridgeNetwork from "./models/IrcBridgeNetwork";
|
||||
import StickerPack from "./models/StickerPack";
|
||||
import Sticker from "./models/Sticker";
|
||||
import UserStickerPack from "./models/UserStickerPack";
|
||||
import WebhookBridgeRecord from "./models/WebhookBridgeRecord";
|
||||
|
||||
class _DimensionStore {
|
||||
private sequelize: Sequelize;
|
||||
@ -53,6 +54,7 @@ class _DimensionStore {
|
||||
StickerPack,
|
||||
Sticker,
|
||||
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 BridgeRecord from "../db/models/BridgeRecord";
|
||||
import { AvailableNetworks, LinkedChannels } from "../bridges/IrcBridge";
|
||||
import { WebhookConfiguration } from "../bridges/models/webhooks";
|
||||
|
||||
export class Bridge extends Integration {
|
||||
constructor(bridge: BridgeRecord, public config: any) {
|
||||
@ -20,4 +21,8 @@ export class Bridge extends Integration {
|
||||
export interface IrcBridgeConfiguration {
|
||||
availableNetworks: AvailableNetworks;
|
||||
links: LinkedChannels;
|
||||
}
|
||||
|
||||
export interface WebhookBridgeConfiguration {
|
||||
webhooks: WebhookConfiguration[];
|
||||
}
|
Loading…
Reference in New Issue
Block a user