mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-09-28 19:55:41 +00:00
Add Jitsi screensharing support
Self-hosted Jitsi Meet servers are possible for those running Dimension. The default server is Riot.IM's server. Adds #87
This commit is contained in:
parent
99bc1eb660
commit
993dcdac21
15
config/integrations/jitsi_widget.yaml
Normal file
15
config/integrations/jitsi_widget.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# All this configuration does is make "Jitsi Widget" available in the UI
|
||||
type: "widget"
|
||||
integrationType: "jitsi"
|
||||
enabled: true
|
||||
name: "Jitsi"
|
||||
about: "Add video conferencing to your room with Jitsi"
|
||||
avatar: "img/avatars/jitsi.png"
|
||||
|
||||
# This is the domain that will be used to construct the Jitsi widget. It must be just the domain.
|
||||
# The default is meet.jit.si The Riot.IM instance is at jitsi.riot.im
|
||||
jitsiDomain: "jitsi.riot.im"
|
||||
|
||||
# This is the path to the external API script. Usually the domain can be replaced with your custom
|
||||
# domain above without any other modifications.
|
||||
scriptUrl: "https://jitsi.riot.im/libs/external_api.min.js"
|
75
package-lock.json
generated
75
package-lock.json
generated
@ -5,81 +5,81 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.4.6.tgz",
|
||||
"integrity": "sha1-+mYYmaik44y3xYPHpcl85l1ZKjU=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.1.0.tgz",
|
||||
"integrity": "sha512-s0tV6y2D16CQAcXjv8CN8AahHb+LoWm9KAUkxvSJ18ZZQweuAY4T8jlRB95ODRFFKfjwyRD9HqXKUC5yHmG9ww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/common": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-4.4.6.tgz",
|
||||
"integrity": "sha1-S4FCByTggooOg5uVpV6xp+g5GPI=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-5.1.0.tgz",
|
||||
"integrity": "sha512-J6E0OfTJJGcyoKU51ZucsDFV40YEAPgP6VCIPYECgOFHxrqg6O1ZFZSD1fdviMXMLVEFCi6Fy6IB7GJyiWgDIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/compiler": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.4.6.tgz",
|
||||
"integrity": "sha1-LuH68lt1fh0SiXkHS+f65SmzvCA=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.1.0.tgz",
|
||||
"integrity": "sha512-iyFQqmhKNRSc9JRx7ty6z/wCsypjpbRu0QR6q2LMa6imuCt9qLHOvTajBQExRB8guqd/LTVDG4WiYY1lf8iO2w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/core": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-4.4.6.tgz",
|
||||
"integrity": "sha1-EwMf0Q3P5DiHVBmzjyESCVi8I1Q=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-5.1.0.tgz",
|
||||
"integrity": "sha512-duObjve+INoz4wWuqcaJzl1isUyI37RtRblTFXgZBp2n2n0nXJq1CubcfgxQhMMR2d64xWLKg9+d34PvnzaMmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/forms": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-4.4.6.tgz",
|
||||
"integrity": "sha1-/mSs5CQ1wbgPSQNLfEHOjK8UpEo=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.1.0.tgz",
|
||||
"integrity": "sha512-2sJqtMht/6vbFg6HwFs0MX4pRhgLt7h2pa6oTH4oBoQ2UF67jCuq4cMljDm9SVxrGw0Q83+/eBk3ER4QnKk48Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/http": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-4.4.6.tgz",
|
||||
"integrity": "sha1-CvaAxnEL3AJtlA4iXP0PalwAXQw=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-5.1.0.tgz",
|
||||
"integrity": "sha512-ltSs52OYnWZJEnbxtHoN5LQiH/37F3GxN6iL0TsQdSlw8HzrdcdbmebKlCpfXwhgcgZC48KWbKSaOs5/xVurfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/platform-browser": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.4.6.tgz",
|
||||
"integrity": "sha1-qYOcVH4bZU+h0kqJeAyLpquNzOA=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.1.0.tgz",
|
||||
"integrity": "sha512-8aeppeASwQv4Fj3B8KBiFHQrKPrwA328AEhlH/HnggCvt0CFffIs2PSqzJBwnOfFWvhFZk020W51B8jrHLQyoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/platform-browser-dynamic": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.6.tgz",
|
||||
"integrity": "sha1-TT2aanvyzz3kBYphWuBZ7/ZB+jY=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.1.0.tgz",
|
||||
"integrity": "sha512-f6Iv4NCYQwBkNeyInZzja8pg0nfUOrxx5H5rEvr0J1bwag2eDofGVPOftha7LDOLVALVOQQiXQBePATMNLB85g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
}
|
||||
},
|
||||
"@angular/router": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-4.4.6.tgz",
|
||||
"integrity": "sha1-D2rSmuD/jSyeo3m9MgRHIXt+yGY=",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-5.1.0.tgz",
|
||||
"integrity": "sha512-CtOwqeo1IUk4kUs+tUggkYFmuu2fPTZ1G/GP7YK6gd3Jr9OtkMFB7wkmnd5YcaYo3wVeYkJWZdJQAvj6OakMww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.8.1"
|
||||
@ -111,6 +111,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz",
|
||||
"integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w=="
|
||||
},
|
||||
"@types/jquery": {
|
||||
"version": "3.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.16.tgz",
|
||||
"integrity": "sha512-q2WC02YxQoX2nY1HRKlYGHpGP1saPmD7GN0pwCDlTz35a4eOtJG+aHRlXyjCuXokUukSrR2aXyBhSW3j+jPc0A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "6.0.92",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.92.tgz",
|
||||
@ -2923,6 +2929,15 @@
|
||||
"minimatch": "3.0.4"
|
||||
}
|
||||
},
|
||||
"goby": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/goby/-/goby-1.1.2.tgz",
|
||||
"integrity": "sha1-ca6JCCSWCjhLvROhoY5/8gI/3YM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ramda": "0.19.1"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
@ -6970,6 +6985,12 @@
|
||||
"integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
|
||||
"dev": true
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.19.1.tgz",
|
||||
"integrity": "sha1-icStaXJl/2sfrOnyhkOeJSDWZ5w=",
|
||||
"dev": true
|
||||
},
|
||||
"random-string": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/random-string/-/random-string-0.2.0.tgz",
|
||||
|
20
package.json
20
package.json
@ -42,18 +42,19 @@
|
||||
"winston": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/animations": "^4.4.6",
|
||||
"@angular/common": "^4.4.6",
|
||||
"@angular/compiler": "^4.4.6",
|
||||
"@angular/core": "^4.4.6",
|
||||
"@angular/forms": "^4.4.6",
|
||||
"@angular/http": "^4.4.6",
|
||||
"@angular/platform-browser": "^4.4.6",
|
||||
"@angular/platform-browser-dynamic": "^4.4.6",
|
||||
"@angular/router": "^4.4.6",
|
||||
"@angular/animations": "^5.0.0",
|
||||
"@angular/common": "^5.0.0",
|
||||
"@angular/compiler": "^5.0.0",
|
||||
"@angular/core": "^5.0.0",
|
||||
"@angular/forms": "^5.0.0",
|
||||
"@angular/http": "^5.0.0",
|
||||
"@angular/platform-browser": "^5.0.0",
|
||||
"@angular/platform-browser-dynamic": "^5.0.0",
|
||||
"@angular/router": "^5.0.0",
|
||||
"@angularclass/hmr": "^2.1.0",
|
||||
"@angularclass/hmr-loader": "^3.0.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.7",
|
||||
"@types/jquery": "^3.2.16",
|
||||
"@types/node": "^6.0.92",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"angular2-toaster": "^4.0.0",
|
||||
@ -66,6 +67,7 @@
|
||||
"cssnano": "^3.10.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.5",
|
||||
"goby": "^1.1.2",
|
||||
"html-loader": "^0.5.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jquery": "^3.2.1",
|
||||
|
@ -32,6 +32,7 @@ class DimensionApi {
|
||||
app.put("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._updateIntegrationState.bind(this));
|
||||
app.get("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._getIntegrationState.bind(this));
|
||||
app.get("/api/v1/dimension/widgets/embeddable", this._checkEmbeddable.bind(this));
|
||||
app.get("/api/v1/dimension/integration/:type/:integrationType", this._getIntegration.bind(this));
|
||||
}
|
||||
|
||||
_checkEmbeddable(req, res) {
|
||||
@ -101,7 +102,7 @@ class DimensionApi {
|
||||
});
|
||||
}
|
||||
|
||||
_getIntegration(integrationConfig, roomId, scalarToken) {
|
||||
_findIntegration(integrationConfig, roomId, scalarToken) {
|
||||
var factory = IntegrationImpl.getFactory(integrationConfig);
|
||||
if (!factory) throw new Error("Missing config factory for " + integrationConfig.name);
|
||||
|
||||
@ -112,6 +113,27 @@ class DimensionApi {
|
||||
}
|
||||
}
|
||||
|
||||
_getIntegration(req, res) {res.setHeader("Content-Type", "application/json");
|
||||
// Unauthed endpoint.
|
||||
|
||||
var type = req.params.type;
|
||||
var integrationType = req.params.integrationType;
|
||||
|
||||
if (!type || !integrationType) {
|
||||
res.status(400).send({error: "Missing integration type or type"});
|
||||
return;
|
||||
}
|
||||
|
||||
var byIntegrationType = Integrations.byType[type];
|
||||
if (!byIntegrationType || !byIntegrationType[integrationType]) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
var integrationConfig = byIntegrationType[integrationType];
|
||||
|
||||
res.status(200).send(integrationConfig);
|
||||
}
|
||||
|
||||
_getIntegrations(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
@ -129,7 +151,7 @@ class DimensionApi {
|
||||
var remove = [];
|
||||
_.forEach(integrations, integration => {
|
||||
try {
|
||||
promises.push(this._getIntegration(integration, roomId, scalarToken).then(builtIntegration => {
|
||||
promises.push(this._findIntegration(integration, roomId, scalarToken).then(builtIntegration => {
|
||||
return builtIntegration.getState().then(state => {
|
||||
var keys = _.keys(state);
|
||||
for (var key of keys) {
|
||||
@ -187,7 +209,7 @@ class DimensionApi {
|
||||
log.info("DimensionApi", "Remove requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._getIntegration(integrationConfig, roomId, scalarToken);
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => integration.removeFromRoom(roomId)).then(() => {
|
||||
res.status(200).send({success: true});
|
||||
}).catch(err => {
|
||||
@ -217,7 +239,7 @@ class DimensionApi {
|
||||
log.info("DimensionApi", "Update state requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._getIntegration(integrationConfig, roomId, scalarToken);
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => {
|
||||
return integration.updateState(req.body.state);
|
||||
}).then(newState => {
|
||||
@ -249,7 +271,7 @@ class DimensionApi {
|
||||
log.info("DimensionApi", "State requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._getIntegration(integrationConfig, roomId, scalarToken);
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => {
|
||||
return integration.getState();
|
||||
}).then(state => {
|
||||
|
@ -31,6 +31,8 @@ import { YoutubeWidgetConfigComponent } from "./configs/widget/youtube/youtube-c
|
||||
import { TwitchWidgetConfigComponent } from "./configs/widget/twitch/twitch-config.component";
|
||||
import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpad-config.component";
|
||||
import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component";
|
||||
import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi-config.component";
|
||||
import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -63,6 +65,8 @@ import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.compo
|
||||
TwitchWidgetConfigComponent,
|
||||
EtherpadWidgetConfigComponent,
|
||||
VideoWidgetWrapperComponent,
|
||||
JitsiWidgetConfigComponent,
|
||||
JitsiWidgetWrapperComponent,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
@ -84,6 +88,7 @@ import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.compo
|
||||
YoutubeWidgetConfigComponent,
|
||||
TwitchWidgetConfigComponent,
|
||||
EtherpadWidgetConfigComponent,
|
||||
JitsiWidgetConfigComponent,
|
||||
]
|
||||
})
|
||||
export class AppModule {
|
||||
|
@ -3,12 +3,14 @@ import { HomeComponent } from "./home/home.component";
|
||||
import { RiotComponent } from "./riot/riot.component";
|
||||
import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component";
|
||||
import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component";
|
||||
import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", component: HomeComponent},
|
||||
{path: "riot", component: RiotComponent},
|
||||
{path: "widgets/generic", component: GenericWidgetWrapperComponent},
|
||||
{path: "widgets/video", component: VideoWidgetWrapperComponent},
|
||||
{path: "widgets/jitsi", component: JitsiWidgetWrapperComponent},
|
||||
];
|
||||
|
||||
export const routing = RouterModule.forRoot(routes);
|
||||
|
60
web/app/configs/widget/jitsi/jitsi-config.component.html
Normal file
60
web/app/configs/widget/jitsi/jitsi-config.component.html
Normal file
@ -0,0 +1,60 @@
|
||||
<div class="config-wrapper">
|
||||
<img src="/img/close.svg" (click)="dialog.close()" class="close-icon">
|
||||
<div class="config-header">
|
||||
<img src="/img/avatars/jitsi.png">
|
||||
<h4>Configure Jitsi Conferences</h4>
|
||||
</div>
|
||||
<div class="config-content" *ngIf="isLoading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p><i class="fa fa-circle-notch fa-spin"></i> Loading widgets...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-content" *ngIf="!isLoading">
|
||||
<form (submit)="validateAndAddWidget()" novalidate name="addForm">
|
||||
<div class="row">
|
||||
<div class="col-md-8" style="margin-bottom: 12px;">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon">https://{{ integration.jitsiDomain }}/</span>
|
||||
<input type="text" class="form-control"
|
||||
placeholder="MyConferenceName"
|
||||
[(ngModel)]="newWidgetName" name="newWidgetName"
|
||||
[disabled]="isUpdating">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-success" [disabled]="isUpdating">
|
||||
<i class="fa fa-plus-circle"></i> Add
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 removable widget-item" *ngFor="let widget of widgets trackById">
|
||||
{{ widget.data.dimOriginalConferenceUrl }} <span class="text-muted" *ngIf="widget.ownerId">(added by {{ widget.ownerId }})</span>
|
||||
<button type="button" class="btn btn-outline-info btn-sm" (click)="editWidget(widget)"
|
||||
style="margin-top: -5px;" [disabled]="isUpdating">
|
||||
<i class="fa fa-pencil"></i> Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="removeWidget(widget)"
|
||||
style="margin-top: -5px;" [disabled]="isUpdating">
|
||||
<i class="fa fa-times"></i> Remove
|
||||
</button>
|
||||
<div *ngIf="isWidgetToggled(widget)">
|
||||
<label class="col-md-8" style="padding-left: 0; margin-left: 0;">
|
||||
Conference URL
|
||||
<input type="text" class="form-control"
|
||||
placeholder="https://jitsi.riot.im/MyConference"
|
||||
[(ngModel)]="widget.data.dimConferenceUrl" name="widget-url-{{widget.id}}"
|
||||
[disabled]="isUpdating">
|
||||
</label>
|
||||
<button type="button" class="btn btn-primary btn-sm" (click)="validateAndSaveWidget(widget)">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline btn-sm" (click)="toggleWidget(widget)">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
4
web/app/configs/widget/jitsi/jitsi-config.component.scss
Normal file
4
web/app/configs/widget/jitsi/jitsi-config.component.scss
Normal file
@ -0,0 +1,4 @@
|
||||
// component styles are encapsulated and only applied to their components
|
||||
.widget-item {
|
||||
margin-top: 3px;
|
||||
}
|
98
web/app/configs/widget/jitsi/jitsi-config.component.ts
Normal file
98
web/app/configs/widget/jitsi/jitsi-config.component.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { DialogRef, ModalComponent } from "ngx-modialog";
|
||||
import { WidgetComponent } from "../widget.component";
|
||||
import { ScalarService } from "../../../shared/scalar.service";
|
||||
import { ConfigModalContext } from "../../../integration/integration.component";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
import { Widget, WIDGET_DIM_JITSI, WIDGET_SCALAR_JITSI } from "../../../shared/models/widget";
|
||||
import { JitsiWidgetIntegration } from "../../../shared/models/integration";
|
||||
import * as gobyInit from "goby";
|
||||
import * as url from "url";
|
||||
|
||||
const goby = gobyInit.init({
|
||||
// Converts words to a url-safe name
|
||||
// Ie: "hello world how-are you" becomes "HelloWorldHowAreYou"
|
||||
decorator: parts => parts.map(p => p ? p.split('-').map(p2 => p2 ? p2[0].toUpperCase() + p2.substring(1).toLowerCase() : '').join('') : '').join(''),
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: "my-jitsi-config",
|
||||
templateUrl: "jitsi-config.component.html",
|
||||
styleUrls: ["jitsi-config.component.scss", "./../../config.component.scss"],
|
||||
})
|
||||
export class JitsiWidgetConfigComponent extends WidgetComponent implements ModalComponent<ConfigModalContext> {
|
||||
|
||||
public integration: JitsiWidgetIntegration;
|
||||
|
||||
constructor(public dialog: DialogRef<ConfigModalContext>,
|
||||
toaster: ToasterService,
|
||||
scalarService: ScalarService,
|
||||
window: Window) {
|
||||
super(
|
||||
toaster,
|
||||
scalarService,
|
||||
dialog.context.roomId,
|
||||
window,
|
||||
WIDGET_DIM_JITSI,
|
||||
WIDGET_SCALAR_JITSI,
|
||||
dialog.context.integrationId,
|
||||
"Jitsi Video Conference",
|
||||
"" // we intentionally don't specify the wrapper so we can control the behaviour
|
||||
);
|
||||
|
||||
this.integration = <JitsiWidgetIntegration>dialog.context.integration;
|
||||
this.newWidgetName = this.generateConferenceId();
|
||||
}
|
||||
|
||||
protected finishParsing(widget: Widget): Widget {
|
||||
const parsedUrl = url.parse(widget.url, true);
|
||||
const conferenceId = parsedUrl.query["confId"];
|
||||
|
||||
if (!widget.data) widget.data = {};
|
||||
|
||||
if (conferenceId) {
|
||||
// It's a scalar widget
|
||||
widget.data.dimOriginalConferenceUrl = "https://jitsi.riot.im/" + conferenceId;
|
||||
widget.data.dimConferenceUrl = widget.data.dimOriginalConferenceUrl;
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
public validateAndAddWidget() {
|
||||
const conferenceUrl = "https://" + this.integration.jitsiDomain + "/" + this.newWidgetName;
|
||||
const conferenceId = this.newWidgetName;
|
||||
const data = {
|
||||
dimOriginalConferenceUrl: conferenceUrl,
|
||||
dimConferenceUrl: conferenceUrl,
|
||||
};
|
||||
|
||||
let widgetQueryString = url.format({
|
||||
query: {
|
||||
//"scriptUrl": this.integration.scriptUrl, // handled in wrapper
|
||||
"domain": this.integration.jitsiDomain,
|
||||
"conferenceId": conferenceId,
|
||||
"displayName": "$matrix_display_name",
|
||||
"avatarUrl": "$matrix_avatar_url",
|
||||
"userId": "$matrix_user_id",
|
||||
},
|
||||
});
|
||||
widgetQueryString = this.unformatParams(widgetQueryString, data);
|
||||
|
||||
this.newWidgetUrl = window.location.origin + "/widgets/jitsi" + widgetQueryString;
|
||||
this.newWidgetName = "Jitsi Video Conference";
|
||||
this.addWidget(data);
|
||||
}
|
||||
|
||||
public validateAndSaveWidget(widget: Widget) {
|
||||
console.log(widget);
|
||||
}
|
||||
|
||||
protected widgetAdded() {
|
||||
this.newWidgetName = this.generateConferenceId();
|
||||
}
|
||||
|
||||
private generateConferenceId() {
|
||||
return goby.generate(["adj", "pre", "suf"]);
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ export class TwitchWidgetConfigComponent extends WidgetComponent implements Moda
|
||||
this.saveWidget(widget);
|
||||
}
|
||||
|
||||
editWidget(widget: Widget) {
|
||||
public editWidget(widget: Widget) {
|
||||
widget.data.newDimChannelName = widget.data.dimChannelName;
|
||||
super.editWidget(widget);
|
||||
}
|
||||
|
@ -33,11 +33,14 @@ export class WidgetComponent {
|
||||
scalarWrapperId = null) {
|
||||
this.isLoading = true;
|
||||
this.isUpdating = false;
|
||||
this.wrapperUrl = window.location.origin + "/widgets/" + wrapperId + "?url=";
|
||||
|
||||
if (!scalarWrapperId) scalarWrapperId = wrapperId;
|
||||
for (let widgetLink of SCALAR_WIDGET_LINKS) {
|
||||
this.scalarWrapperUrls.push(widgetLink.replace("__TYPE__", scalarWrapperId));
|
||||
if (wrapperId) {
|
||||
this.wrapperUrl = window.location.origin + "/widgets/" + wrapperId + "?url=";
|
||||
|
||||
if (!scalarWrapperId) scalarWrapperId = wrapperId;
|
||||
for (let widgetLink of SCALAR_WIDGET_LINKS) {
|
||||
this.scalarWrapperUrls.push(widgetLink.replace("__TYPE__", scalarWrapperId));
|
||||
}
|
||||
}
|
||||
|
||||
this.getWidgetsOfType(primaryWidgetType, alternateWidgetType).then(widgets => {
|
||||
@ -62,6 +65,15 @@ export class WidgetComponent {
|
||||
});
|
||||
}
|
||||
|
||||
protected finishParsing(widget: Widget) {
|
||||
// We don't actually need to do anything
|
||||
return widget;
|
||||
}
|
||||
|
||||
protected widgetAdded() {
|
||||
// Meant to be overridden
|
||||
}
|
||||
|
||||
private getWidgetsOfType(type: string, altType: string): Promise<Widget[]> {
|
||||
return this.scalarApi.getWidgets(this.roomId)
|
||||
.then(resp => ScalarToWidgets(resp))
|
||||
@ -73,11 +85,13 @@ export class WidgetComponent {
|
||||
filtered.push(widget);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
return filtered.map(w => this.finishParsing(w));
|
||||
});
|
||||
}
|
||||
|
||||
private getWrappedUrl(url: string): string {
|
||||
if (!this.wrapperUrl) return url;
|
||||
|
||||
const urls = [this.wrapperUrl].concat(this.scalarWrapperUrls);
|
||||
for (let scalarUrl of urls) {
|
||||
if (url.startsWith(scalarUrl)) {
|
||||
@ -88,18 +102,29 @@ export class WidgetComponent {
|
||||
}
|
||||
|
||||
private wrapUrl(url: string): string {
|
||||
if (!this.wrapperUrl) return url;
|
||||
|
||||
let encodedURL = this.wrapperUrl + encodeURIComponent(url);
|
||||
|
||||
//don't URL encode $vars of the widget Spec
|
||||
//TODO do the same with vars from the data object
|
||||
encodedURL = encodedURL.replace(encodeURIComponent("$matrix_user_id"), "$matrix_user_id");
|
||||
encodedURL = encodedURL.replace(encodeURIComponent("$matrix_room_id"), "$matrix_room_id");
|
||||
encodedURL = encodedURL.replace(encodeURIComponent("$matrix_display_name"), "$matrix_display_name");
|
||||
encodedURL = encodedURL.replace(encodeURIComponent("$matrix_avatar_url"), "$matrix_avatar_url");
|
||||
// TODO: Decode data parameters
|
||||
encodedURL = this.unformatParams(encodedURL);
|
||||
|
||||
return encodedURL;
|
||||
}
|
||||
|
||||
protected unformatParams(encodedUrl: string, additionalData: any = {}):string {
|
||||
encodedUrl = encodedUrl.replace(encodeURIComponent("$matrix_user_id"), "$matrix_user_id");
|
||||
encodedUrl = encodedUrl.replace(encodeURIComponent("$matrix_room_id"), "$matrix_room_id");
|
||||
encodedUrl = encodedUrl.replace(encodeURIComponent("$matrix_display_name"), "$matrix_display_name");
|
||||
encodedUrl = encodedUrl.replace(encodeURIComponent("$matrix_avatar_url"), "$matrix_avatar_url");
|
||||
|
||||
for (const key of Object.keys(additionalData)) {
|
||||
encodedUrl = encodedUrl.replace(encodeURIComponent("$" + key), "$" + key);
|
||||
}
|
||||
|
||||
return encodedUrl;
|
||||
}
|
||||
|
||||
private setWidgetUrl(widget: Widget) {
|
||||
widget.url = this.getWrappedUrl(widget.url);
|
||||
|
||||
@ -127,6 +152,7 @@ export class WidgetComponent {
|
||||
this.newWidgetUrl = "";
|
||||
this.newWidgetName = "";
|
||||
this.toaster.pop("success", "Widget added!");
|
||||
this.widgetAdded();
|
||||
})
|
||||
.catch(err => {
|
||||
this.toaster.pop("error", err.json().error);
|
||||
|
@ -6,7 +6,10 @@ import { ToasterService } from "angular2-toaster";
|
||||
import { Integration } from "../shared/models/integration";
|
||||
import { IntegrationService } from "../shared/integration.service";
|
||||
import * as _ from "lodash";
|
||||
import { WIDGET_DIM_CUSTOM, WIDGET_DIM_ETHERPAD, WIDGET_DIM_TWITCH, WIDGET_DIM_YOUTUBE } from "../shared/models/widget";
|
||||
import {
|
||||
WIDGET_DIM_CUSTOM, WIDGET_DIM_ETHERPAD, WIDGET_DIM_JITSI, WIDGET_DIM_TWITCH,
|
||||
WIDGET_DIM_YOUTUBE
|
||||
} from "../shared/models/widget";
|
||||
import { IntegrationComponent } from "../integration/integration.component";
|
||||
|
||||
@Component({
|
||||
@ -82,6 +85,9 @@ export class RiotComponent {
|
||||
} else if (this.requestedScreen === "type_" + WIDGET_DIM_ETHERPAD) {
|
||||
type = "widget";
|
||||
integrationType = "etherpad";
|
||||
} else if (this.requestedScreen === "type_" + WIDGET_DIM_JITSI) {
|
||||
type = "widget";
|
||||
integrationType = "jitsi";
|
||||
} else {
|
||||
console.log("Unknown screen requested: " + this.requestedScreen);
|
||||
}
|
||||
|
@ -40,4 +40,9 @@ export class ApiService {
|
||||
return this.http.get(url, {params: {url: checkUrl}})
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
|
||||
getIntegration(type: string, integrationType: string): Promise<Integration> {
|
||||
const url = "/api/v1/dimension/integration/" + type + "/" + integrationType;
|
||||
return this.http.get(url).map(res => res.json()).toPromise();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { CustomWidgetConfigComponent } from "../configs/widget/custom_widget/cus
|
||||
import { YoutubeWidgetConfigComponent } from "../configs/widget/youtube/youtube-config.component";
|
||||
import { TwitchWidgetConfigComponent } from "../configs/widget/twitch/twitch-config.component";
|
||||
import { EtherpadWidgetConfigComponent } from "../configs/widget/etherpad/etherpad-config.component";
|
||||
import { JitsiWidgetConfigComponent } from "../configs/widget/jitsi/jitsi-config.component";
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationService {
|
||||
@ -26,6 +27,7 @@ export class IntegrationService {
|
||||
"youtube": true,
|
||||
"twitch": true,
|
||||
"etherpad": true,
|
||||
"jitsi": true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -42,6 +44,7 @@ export class IntegrationService {
|
||||
"youtube": YoutubeWidgetConfigComponent,
|
||||
"twitch": TwitchWidgetConfigComponent,
|
||||
"etherpad": EtherpadWidgetConfigComponent,
|
||||
"jitsi": JitsiWidgetConfigComponent,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -31,3 +31,8 @@ export interface IRCIntegration extends Integration {
|
||||
export interface EtherpadWidgetIntegration extends Integration {
|
||||
defaultUrl: string;
|
||||
}
|
||||
|
||||
export interface JitsiWidgetIntegration extends Integration {
|
||||
jitsiDomain: string;
|
||||
scriptUrl: string
|
||||
}
|
@ -15,6 +15,7 @@ export const WIDGET_DIM_CUSTOM = "dimension-customwidget";
|
||||
export const WIDGET_DIM_YOUTUBE = "dimension-youtube";
|
||||
export const WIDGET_DIM_TWITCH = "dimension-twitch";
|
||||
export const WIDGET_DIM_ETHERPAD = "dimension-etherpad";
|
||||
export const WIDGET_DIM_JITSI = "dimension-jitsi";
|
||||
|
||||
export interface Widget {
|
||||
id: string;
|
||||
|
13
web/app/widget_wrappers/jitsi/jitsi.component.html
Normal file
13
web/app/widget_wrappers/jitsi/jitsi.component.html
Normal file
@ -0,0 +1,13 @@
|
||||
<div id="jitsiContainer">
|
||||
</div>
|
||||
|
||||
<div class="join-conference-wrapper">
|
||||
<div class="join-conference-boat">
|
||||
<div *ngIf="!isJoined" class="join-conference-prompt">
|
||||
<h3>Jitsi Video Conference</h3>
|
||||
<button type="button" (click)="joinConference()" class="btn btn-primary btn-large">
|
||||
Join Conference
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
28
web/app/widget_wrappers/jitsi/jitsi.component.scss
Normal file
28
web/app/widget_wrappers/jitsi/jitsi.component.scss
Normal file
@ -0,0 +1,28 @@
|
||||
// component styles are encapsulated and only applied to their components
|
||||
#jitsiContainer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.join-conference-wrapper {
|
||||
display: table;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.join-conference-boat {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.join-conference-prompt {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
}
|
71
web/app/widget_wrappers/jitsi/jitsi.component.ts
Normal file
71
web/app/widget_wrappers/jitsi/jitsi.component.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import * as $ from "jquery";
|
||||
import { ApiService } from "../../shared/api.service";
|
||||
import { JitsiWidgetIntegration } from "../../shared/models/integration";
|
||||
|
||||
declare var JitsiMeetExternalAPI: any;
|
||||
|
||||
@Component({
|
||||
selector: "my-jitsi-widget-wrapper",
|
||||
templateUrl: "jitsi.component.html",
|
||||
styleUrls: ["jitsi.component.scss"],
|
||||
})
|
||||
export class JitsiWidgetWrapperComponent implements OnInit {
|
||||
|
||||
public isJoined = false;
|
||||
|
||||
private domain: string;
|
||||
private conferenceId: string;
|
||||
private displayName: string;
|
||||
private avatarUrl: string;
|
||||
private userId: string;
|
||||
private jitsiApiObj: any;
|
||||
|
||||
constructor(activatedRoute: ActivatedRoute, private api: ApiService) {
|
||||
let params: any = activatedRoute.snapshot.queryParams;
|
||||
|
||||
this.domain = params.domain;
|
||||
this.conferenceId = params.conferenceId;
|
||||
this.displayName = params.displayName;
|
||||
this.avatarUrl = params.avatarUrl;
|
||||
this.userId = params.userId;
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.api.getIntegration("widget", "jitsi").then(integration => {
|
||||
const widget = <JitsiWidgetIntegration>integration;
|
||||
$.getScript(widget.scriptUrl);
|
||||
});
|
||||
}
|
||||
|
||||
public joinConference() {
|
||||
$(".join-conference-wrapper").hide();
|
||||
$("#jitsiContainer").show();
|
||||
|
||||
this.jitsiApiObj = new JitsiMeetExternalAPI(this.domain, {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: document.querySelector("#jitsiContainer"),
|
||||
roomName: this.conferenceId,
|
||||
interfaceConfigOverwrite: {
|
||||
SHOW_JITSI_WATERMARK: false,
|
||||
SHOW_WATERMARK_FOR_GUESTS: false,
|
||||
MAIN_TOOLBAR_BUTTONS: [],
|
||||
VIDEO_LAYOUT_FIT: "height",
|
||||
}
|
||||
});
|
||||
if (this.displayName) this.jitsiApiObj.executeCommand("displayName", this.displayName);
|
||||
if (this.avatarUrl) this.jitsiApiObj.executeCommand("avatarUrl", this.avatarUrl.toString());
|
||||
if (this.userId) this.jitsiApiObj.executeCommand("email", this.userId);
|
||||
|
||||
this.jitsiApiObj.on("readyToClose", () => {
|
||||
this.isJoined = false;
|
||||
$(".join-conference-wrapper").show();
|
||||
$("#jitsiContainer").hide().html("");
|
||||
});
|
||||
|
||||
this.isJoined = true;
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
web/public/img/avatars/jitsi.png
Normal file
BIN
web/public/img/avatars/jitsi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@ -5,4 +5,4 @@ import "@angular/common";
|
||||
import "@angular/http";
|
||||
import "@angular/router";
|
||||
import "rxjs";
|
||||
import "@angularclass/hmr";
|
||||
import "@angularclass/hmr";
|
Loading…
Reference in New Issue
Block a user