mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-08-31 06:02:09 +00:00
Add the frontend for Gitter bridging
Fixes https://github.com/turt2live/matrix-dimension/issues/4 Fixes https://github.com/turt2live/matrix-dimension/issues/7
This commit is contained in:
parent
2e844a707f
commit
edbbd3b8c0
61
src/api/dimension/DimensionGitterService.ts
Normal file
61
src/api/dimension/DimensionGitterService.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { DELETE, GET, Path, PathParam, POST, QueryParam } from "typescript-rest";
|
||||||
|
import { ScalarService } from "../scalar/ScalarService";
|
||||||
|
import { ApiError } from "../ApiError";
|
||||||
|
import { BridgedRoom, GitterBridge } from "../../bridges/GitterBridge";
|
||||||
|
import { LogService } from "matrix-js-snippets";
|
||||||
|
|
||||||
|
interface BridgeRoomRequest {
|
||||||
|
gitterRoomName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for interacting with the Gitter bridge
|
||||||
|
*/
|
||||||
|
@Path("/api/v1/dimension/gitter")
|
||||||
|
export class DimensionGitterService {
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("room/:roomId/link")
|
||||||
|
public async getLink(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise<BridgedRoom> {
|
||||||
|
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gitter = new GitterBridge(userId);
|
||||||
|
return gitter.getLink(roomId);
|
||||||
|
} catch (e) {
|
||||||
|
LogService.error("DimensionGitterService", e);
|
||||||
|
throw new ApiError(400, "Error getting bridge info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("room/:roomId/link")
|
||||||
|
public async bridgeRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string, request: BridgeRoomRequest): Promise<BridgedRoom> {
|
||||||
|
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gitter = new GitterBridge(userId);
|
||||||
|
await gitter.requestLink(roomId, request.gitterRoomName);
|
||||||
|
return gitter.getLink(roomId);
|
||||||
|
} catch (e) {
|
||||||
|
LogService.error("DimensionGitterService", e);
|
||||||
|
throw new ApiError(400, "Error bridging room");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("room/:roomId/link")
|
||||||
|
public async unbridgeRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise<any> {
|
||||||
|
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gitter = new GitterBridge(userId);
|
||||||
|
const link = await gitter.getLink(roomId);
|
||||||
|
await gitter.removeLink(roomId, link.gitterRoomName);
|
||||||
|
return {}; // 200 OK
|
||||||
|
} catch (e) {
|
||||||
|
LogService.error("DimensionGitterService", e);
|
||||||
|
throw new ApiError(400, "Error unbridging room");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ export class GitterBridge {
|
|||||||
const bridge = await this.getDefaultBridge();
|
const bridge = await this.getDefaultBridge();
|
||||||
|
|
||||||
if (bridge.upstreamId) {
|
if (bridge.upstreamId) {
|
||||||
const info = await this.doUpstreamRequest<ModularGitterResponse<GetBotUserIdResponse>>(bridge, "POST", "/bridges/gitter/_matrix/provision/getbotid");
|
const info = await this.doUpstreamRequest<ModularGitterResponse<GetBotUserIdResponse>>(bridge, "POST", "/bridges/gitter/_matrix/provision/getbotid/", null, {});
|
||||||
if (!info || !info.replies || !info.replies[0] || !info.replies[0].response) {
|
if (!info || !info.replies || !info.replies[0] || !info.replies[0].response) {
|
||||||
throw new Error("Invalid response from Modular for Gitter bot user ID");
|
throw new Error("Invalid response from Modular for Gitter bot user ID");
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ export class GitterBridge {
|
|||||||
try {
|
try {
|
||||||
if (bridge.upstreamId) {
|
if (bridge.upstreamId) {
|
||||||
delete requestBody["user_id"];
|
delete requestBody["user_id"];
|
||||||
const link = await this.doUpstreamRequest<ModularGitterResponse<BridgedRoomResponse>>(bridge, "POST", "/bridge/gitter/_matrix/provision/getlink", null, requestBody);
|
const link = await this.doUpstreamRequest<ModularGitterResponse<BridgedRoomResponse>>(bridge, "POST", "/bridges/gitter/_matrix/provision/getlink", null, requestBody);
|
||||||
if (!link || !link.replies || !link.replies[0] || !link.replies[0].response) {
|
if (!link || !link.replies || !link.replies[0] || !link.replies[0].response) {
|
||||||
// noinspection ExceptionCaughtLocallyJS
|
// noinspection ExceptionCaughtLocallyJS
|
||||||
throw new Error("Invalid response from Modular for Gitter list links in " + roomId);
|
throw new Error("Invalid response from Modular for Gitter list links in " + roomId);
|
||||||
@ -76,6 +76,7 @@ export class GitterBridge {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (e.status === 404) return null;
|
||||||
LogService.error("GitterBridge", e);
|
LogService.error("GitterBridge", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -147,9 +148,10 @@ export class GitterBridge {
|
|||||||
LogService.error("GitterBridge", "There is no response for " + url);
|
LogService.error("GitterBridge", "There is no response for " + url);
|
||||||
reject(new Error("No response provided - is the service online?"));
|
reject(new Error("No response provided - is the service online?"));
|
||||||
} else if (res.statusCode !== 200) {
|
} else if (res.statusCode !== 200) {
|
||||||
|
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
||||||
LogService.error("GitterBridge", "Got status code " + res.statusCode + " when calling " + url);
|
LogService.error("GitterBridge", "Got status code " + res.statusCode + " when calling " + url);
|
||||||
LogService.error("GitterBridge", res.body);
|
LogService.error("GitterBridge", res.body);
|
||||||
reject(new Error("Request failed"));
|
reject({body: res.body, status: res.statusCode});
|
||||||
} else {
|
} else {
|
||||||
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
||||||
resolve(res.body);
|
resolve(res.body);
|
||||||
@ -179,9 +181,10 @@ export class GitterBridge {
|
|||||||
LogService.error("GitterBridge", "There is no response for " + url);
|
LogService.error("GitterBridge", "There is no response for " + url);
|
||||||
reject(new Error("No response provided - is the service online?"));
|
reject(new Error("No response provided - is the service online?"));
|
||||||
} else if (res.statusCode !== 200) {
|
} else if (res.statusCode !== 200) {
|
||||||
|
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
||||||
LogService.error("GitterBridge", "Got status code " + res.statusCode + " when calling " + url);
|
LogService.error("GitterBridge", "Got status code " + res.statusCode + " when calling " + url);
|
||||||
LogService.error("GitterBridge", res.body);
|
LogService.error("GitterBridge", res.body);
|
||||||
reject(new Error("Request failed"));
|
reject({body: res.body, status: res.statusCode});
|
||||||
} else {
|
} else {
|
||||||
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
if (typeof(res.body) === "string") res.body = JSON.parse(res.body);
|
||||||
resolve(res.body);
|
resolve(res.body);
|
||||||
|
@ -5,12 +5,14 @@ import { PortalInfo, PuppetInfo } from "../bridges/TelegramBridge";
|
|||||||
import { WebhookConfiguration } from "../bridges/models/webhooks";
|
import { WebhookConfiguration } from "../bridges/models/webhooks";
|
||||||
import { BridgedRoom } from "../bridges/GitterBridge";
|
import { BridgedRoom } from "../bridges/GitterBridge";
|
||||||
|
|
||||||
|
const PRIVATE_ACCESS_SUPPORTED_BRIDGES = ["webhooks", "gitter"];
|
||||||
|
|
||||||
export class Bridge extends Integration {
|
export class Bridge extends Integration {
|
||||||
constructor(bridge: BridgeRecord, public config: any) {
|
constructor(bridge: BridgeRecord, public config: any) {
|
||||||
super(bridge);
|
super(bridge);
|
||||||
this.category = "bridge";
|
this.category = "bridge";
|
||||||
|
|
||||||
if (bridge.type === "webhooks") this.requirements = [];
|
if (PRIVATE_ACCESS_SUPPORTED_BRIDGES.indexOf(bridge.type) !== -1) this.requirements = [];
|
||||||
else this.requirements = [{
|
else this.requirements = [{
|
||||||
condition: "publicRoom",
|
condition: "publicRoom",
|
||||||
expectedValue: true,
|
expectedValue: true,
|
||||||
|
@ -93,6 +93,8 @@ import { WebhooksBridgeConfigComponent } from "./configs/bridge/webhooks/webhook
|
|||||||
import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.component";
|
import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.component";
|
||||||
import { AdminGitterBridgeManageSelfhostedComponent } from "./admin/bridges/gitter/manage-selfhosted/manage-selfhosted.component";
|
import { AdminGitterBridgeManageSelfhostedComponent } from "./admin/bridges/gitter/manage-selfhosted/manage-selfhosted.component";
|
||||||
import { AdminGitterApiService } from "./shared/services/admin/admin-gitter-api.service";
|
import { AdminGitterApiService } from "./shared/services/admin/admin-gitter-api.service";
|
||||||
|
import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.bridge.component";
|
||||||
|
import { GitterApiService } from "./shared/services/integrations/gitter-api.service";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -170,6 +172,7 @@ import { AdminGitterApiService } from "./shared/services/admin/admin-gitter-api.
|
|||||||
WebhooksBridgeConfigComponent,
|
WebhooksBridgeConfigComponent,
|
||||||
AdminGitterBridgeComponent,
|
AdminGitterBridgeComponent,
|
||||||
AdminGitterBridgeManageSelfhostedComponent,
|
AdminGitterBridgeManageSelfhostedComponent,
|
||||||
|
GitterBridgeConfigComponent,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
@ -194,6 +197,7 @@ import { AdminGitterApiService } from "./shared/services/admin/admin-gitter-api.
|
|||||||
AdminWebhooksApiService,
|
AdminWebhooksApiService,
|
||||||
WebhooksApiService,
|
WebhooksApiService,
|
||||||
AdminGitterApiService,
|
AdminGitterApiService,
|
||||||
|
GitterApiService,
|
||||||
{provide: Window, useValue: window},
|
{provide: Window, useValue: window},
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
|
@ -32,6 +32,7 @@ import { TelegramBridgeConfigComponent } from "./configs/bridge/telegram/telegra
|
|||||||
import { AdminWebhooksBridgeComponent } from "./admin/bridges/webhooks/webhooks.component";
|
import { AdminWebhooksBridgeComponent } from "./admin/bridges/webhooks/webhooks.component";
|
||||||
import { WebhooksBridgeConfigComponent } from "./configs/bridge/webhooks/webhooks.bridge.component";
|
import { WebhooksBridgeConfigComponent } from "./configs/bridge/webhooks/webhooks.bridge.component";
|
||||||
import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.component";
|
import { AdminGitterBridgeComponent } from "./admin/bridges/gitter/gitter.component";
|
||||||
|
import { GitterBridgeConfigComponent } from "./configs/bridge/gitter/gitter.bridge.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "", component: HomeComponent},
|
{path: "", component: HomeComponent},
|
||||||
@ -194,6 +195,11 @@ const routes: Routes = [
|
|||||||
component: WebhooksBridgeConfigComponent,
|
component: WebhooksBridgeConfigComponent,
|
||||||
data: {breadcrumb: "Webhook Bridge Configuration", name: "Webhook Bridge Configuration"},
|
data: {breadcrumb: "Webhook Bridge Configuration", name: "Webhook Bridge Configuration"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "gitter",
|
||||||
|
component: GitterBridgeConfigComponent,
|
||||||
|
data: {breadcrumb: "Gitter Bridge Configuration", name: "Gitter Bridge Configuration"},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
27
web/app/configs/bridge/gitter/gitter.bridge.component.html
Normal file
27
web/app/configs/bridge/gitter/gitter.bridge.component.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<my-bridge-config [bridgeComponent]="this">
|
||||||
|
<ng-template #bridgeParamsTemplate>
|
||||||
|
<my-ibox [isCollapsible]="false">
|
||||||
|
<h5 class="my-ibox-title">
|
||||||
|
Bridge to Gitter
|
||||||
|
</h5>
|
||||||
|
<div class="my-ibox-content">
|
||||||
|
<div *ngIf="isBridged">
|
||||||
|
This room is bridged to "{{ bridge.config.link.gitterRoomName }}" on Gitter.
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" [disabled]="isBusy" (click)="unbridgeRoom()">
|
||||||
|
Unbridge
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!isBridged">
|
||||||
|
<label class="label-block">
|
||||||
|
Gitter Room
|
||||||
|
<input title="room name" type="text" class="form-control form-control-sm col-md-3"
|
||||||
|
[(ngModel)]="gitterRoomName" [disabled]="isBusy" placeholder="my-org/room" />
|
||||||
|
</label>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" [disabled]="isBusy" (click)="bridgeRoom()">
|
||||||
|
Bridge
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</my-ibox>
|
||||||
|
</ng-template>
|
||||||
|
</my-bridge-config>
|
@ -0,0 +1,4 @@
|
|||||||
|
.actions-col {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
66
web/app/configs/bridge/gitter/gitter.bridge.component.ts
Normal file
66
web/app/configs/bridge/gitter/gitter.bridge.component.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { BridgeComponent } from "../bridge.component";
|
||||||
|
import { FE_GitterLink } from "../../../shared/models/gitter";
|
||||||
|
import { GitterApiService } from "../../../shared/services/integrations/gitter-api.service";
|
||||||
|
import { ScalarClientApiService } from "../../../shared/services/scalar/scalar-client-api.service";
|
||||||
|
|
||||||
|
interface GitterConfig {
|
||||||
|
botUserId: string;
|
||||||
|
link: FE_GitterLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "gitter.bridge.component.html",
|
||||||
|
styleUrls: ["gitter.bridge.component.scss"],
|
||||||
|
})
|
||||||
|
export class GitterBridgeConfigComponent extends BridgeComponent<GitterConfig> {
|
||||||
|
|
||||||
|
public gitterRoomName: string;
|
||||||
|
public isBusy: boolean;
|
||||||
|
|
||||||
|
constructor(private gitter: GitterApiService, private scalar: ScalarClientApiService) {
|
||||||
|
super("gitter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isBridged(): boolean {
|
||||||
|
return this.bridge.config.link && !!this.bridge.config.link.gitterRoomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async bridgeRoom(): Promise<any> {
|
||||||
|
this.isBusy = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.scalar.inviteUser(this.roomId, this.bridge.config.botUserId);
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.response || !e.response.error || !e.response.error._error ||
|
||||||
|
e.response.error._error.message.indexOf("already in the room") === -1) {
|
||||||
|
this.isBusy = false;
|
||||||
|
this.toaster.pop("error", "Error inviting bridge");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gitter.bridgeRoom(this.roomId, this.gitterRoomName).then(link => {
|
||||||
|
this.bridge.config.link = link;
|
||||||
|
this.isBusy = false;
|
||||||
|
this.toaster.pop("success", "Bridge requested");
|
||||||
|
}).catch(error => {
|
||||||
|
this.isBusy = false;
|
||||||
|
console.error(error);
|
||||||
|
this.toaster.pop("error", "Error requesting bridge");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public unbridgeRoom(): void {
|
||||||
|
this.isBusy = true;
|
||||||
|
this.gitter.unbridgeRoom(this.roomId).then(() => {
|
||||||
|
this.bridge.config.link = null;
|
||||||
|
this.isBusy = false;
|
||||||
|
this.toaster.pop("success", "Bridge removed");
|
||||||
|
}).catch(error => {
|
||||||
|
this.isBusy = false;
|
||||||
|
console.error(error);
|
||||||
|
this.toaster.pop("error", "Error removing bridge");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -3,4 +3,9 @@ export interface FE_GitterBridge {
|
|||||||
upstreamId?: number;
|
upstreamId?: number;
|
||||||
provisionUrl?: string;
|
provisionUrl?: string;
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FE_GitterLink {
|
||||||
|
roomId: string;
|
||||||
|
gitterRoomName: string;
|
||||||
}
|
}
|
@ -19,6 +19,7 @@ export class IntegrationsRegistry {
|
|||||||
"irc": {},
|
"irc": {},
|
||||||
"telegram": {},
|
"telegram": {},
|
||||||
"webhooks": {},
|
"webhooks": {},
|
||||||
|
"gitter": {},
|
||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"custom": {
|
"custom": {
|
||||||
|
26
web/app/shared/services/integrations/gitter-api.service.ts
Normal file
26
web/app/shared/services/integrations/gitter-api.service.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Http } from "@angular/http";
|
||||||
|
import { AuthedApi } from "../authed-api";
|
||||||
|
import { FE_GitterLink } from "../../models/gitter";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GitterApiService extends AuthedApi {
|
||||||
|
constructor(http: Http) {
|
||||||
|
super(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bridgeRoom(roomId: string, gitterRoomName: string): Promise<FE_GitterLink> {
|
||||||
|
return this.authedPost("/api/v1/dimension/gitter/room/" + roomId + "/link", {gitterRoomName})
|
||||||
|
.map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unbridgeRoom(roomId: string): Promise<any> {
|
||||||
|
return this.authedDelete("/api/v1/dimension/gitter/room/" + roomId + "/link")
|
||||||
|
.map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLink(roomId: string): Promise<FE_GitterLink> {
|
||||||
|
return this.authedGet("/api/v1/dimension/gitter/room/" + roomId + "/link")
|
||||||
|
.map(r => r.json()).toPromise();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user