Add jitsi widgets

This commit is contained in:
Travis Ralston 2017-12-23 14:16:22 -07:00
parent 41c887f390
commit a72177b530
8 changed files with 151 additions and 2 deletions

View file

@ -5,6 +5,8 @@ import { DimensionStore } from "../../db/DimensionStore";
import { DimensionAdminService } from "./DimensionAdminService";
import { Widget } from "../../integrations/Widget";
import { MemoryCache } from "../../MemoryCache";
import { Integration } from "../../integrations/Integration";
import { ApiError } from "../ApiError";
interface IntegrationsResponse {
widgets: Widget[],
@ -42,6 +44,22 @@ export class DimensionIntegrationsService {
return this.getEnabledIntegrations(scalarToken);
}
@GET
@Path(":category/:type")
public getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise<Integration> {
return this.getIntegrations(true).then(response => {
for (const key of Object.keys(response)) {
for (const integration of <Integration[]>response[key]) {
if (integration.category === category && integration.type === type) {
return integration;
}
}
}
throw new ApiError(404, "Integration not found");
});
}
private getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> {
const cachedResponse = DimensionIntegrationsService.integrationCache.get("integrations_" + isEnabledCheck);
if (cachedResponse) {

View file

@ -37,6 +37,7 @@ import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpa
import { NameService } from "./shared/services/name.service";
import { GoogleCalendarWidgetConfigComponent } from "./configs/widget/google_calendar/gcal.widget.component";
import { GoogleDocsWidgetConfigComponent } from "./configs/widget/google_docs/gdoc.widget.component";
import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi.widget.component";
@NgModule({
imports: [
@ -73,6 +74,7 @@ import { GoogleDocsWidgetConfigComponent } from "./configs/widget/google_docs/gd
EtherpadWidgetConfigComponent,
GoogleCalendarWidgetConfigComponent,
GoogleDocsWidgetConfigComponent,
JitsiWidgetConfigComponent,
// Vendor
],

View file

@ -10,6 +10,7 @@ import { CustomWidgetConfigComponent } from "./configs/widget/custom/custom.widg
import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpad.widget.component";
import { GoogleCalendarWidgetConfigComponent } from "./configs/widget/google_calendar/gcal.widget.component";
import { GoogleDocsWidgetConfigComponent } from "./configs/widget/google_docs/gdoc.widget.component";
import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi.widget.component";
const routes: Routes = [
{path: "", component: HomeComponent},
@ -46,6 +47,11 @@ const routes: Routes = [
component: GoogleDocsWidgetConfigComponent,
data: {breadcrumb: "Google Doc Widgets", name: "Google Doc Widgets"}
},
{
path: "jitsi",
component: JitsiWidgetConfigComponent,
data: {breadcrumb: "Jitsi Widgets", name: "Jitsi Widgets"}
},
],
},
],

View file

@ -0,0 +1,11 @@
<my-widget-config [widgetComponent]="this">
<ng-template #widgetParamsTemplate let-widget="widget">
<label class="label-block">
Conference URL
<input type="text" class="form-control"
placeholder="https://jitsi.riot.im/MyCoolConference"
[(ngModel)]="widget.dimension.newData.conferenceUrl" name="widget-url-{{widget.id}}"
[disabled]="isUpdating"/>
</label>
</ng-template>
</my-widget-config>

View file

@ -0,0 +1,89 @@
import { DISABLE_AUTOMATIC_WRAPPING, WidgetComponent } from "../widget.component";
import { EditableWidget, WIDGET_JITSI } from "../../../shared/models/widget";
import { Component } from "@angular/core";
import { JitsiWidget } from "../../../shared/models/integration";
import { SessionStorage } from "../../../shared/SessionStorage";
import { NameService } from "../../../shared/services/name.service";
import * as url from "url";
@Component({
templateUrl: "jitsi.widget.component.html",
styleUrls: ["jitsi.widget.component.scss"],
})
export class JitsiWidgetConfigComponent extends WidgetComponent {
private jitsiWidget: JitsiWidget = <JitsiWidget>SessionStorage.editIntegration;
constructor(private nameService: NameService) {
super(WIDGET_JITSI, "Jitsi Video Conference", DISABLE_AUTOMATIC_WRAPPING);
}
protected OnWidgetsDiscovered(widgets: EditableWidget[]) {
for (const widget of widgets) {
const templatedUrl = this.templateUrl(widget.url, widget.data);
const parsedUrl = url.parse(templatedUrl, true);
const conferenceId = parsedUrl.query["conferenceId"];
const confId = parsedUrl.query["confId"];
const domain = parsedUrl.query["domain"];
let isAudioConf = parsedUrl.query["isAudioConf"];
// Convert isAudioConf to boolean
if (isAudioConf === "true") isAudioConf = true;
else if (isAudioConf === "false") isAudioConf = false;
else if (isAudioConf && isAudioConf[0] === '$') isAudioConf = widget.data[isAudioConf];
else isAudioConf = false; // default
if (conferenceId) {
// It's a legacy Dimension widget
widget.data.confId = conferenceId;
} else widget.data.confId = confId;
if (domain) widget.data.domain = domain;
else widget.data.domain = "jitsi.riot.im";
widget.data.isAudioConf = isAudioConf;
widget.data.conferenceUrl = "https://" + widget.data.domain + "/" + widget.data.confId;
}
}
protected OnNewWidgetPrepared(widget: EditableWidget): void {
const name = this.nameService.getHumanReadableName();
let rootUrl = "https://jitsi.riot.im/";
if (this.jitsiWidget.options && this.jitsiWidget.options.jitsiDomain) {
rootUrl = "https://" + this.jitsiWidget.options.jitsiDomain + "/";
}
widget.dimension.newData["conferenceUrl"] = rootUrl + name;
}
protected OnWidgetBeforeAdd(widget: EditableWidget) {
this.setJitsiUrl(widget);
}
protected OnWidgetBeforeEdit(widget: EditableWidget) {
this.setJitsiUrl(widget);
}
private setJitsiUrl(widget: EditableWidget) {
const jitsiUrl = url.parse(widget.dimension.newData.conferenceUrl);
widget.dimension.newData.domain = jitsiUrl.host;
widget.dimension.newData.confId = jitsiUrl.path.substring(1);
widget.dimension.newData.isAudioConf = false;
let widgetQueryString = url.format({
query: {
// TODO: Use templating when mobile riot supports it
"confId": widget.dimension.newData.confId,
"domain": widget.dimension.newData.domain,
"isAudioConf": widget.dimension.newData.isAudioConf,
"displayName": "$matrix_display_name",
"avatarUrl": "$matrix_avatar_url",
"userId": "$matrix_user_id",
},
});
widgetQueryString = this.decodeParams(widgetQueryString, Object.keys(widget.dimension.newData).map(k => "$" + k));
widget.dimension.newUrl = window.location.origin + "/widgets/jitsi" + widgetQueryString;
}
}

View file

@ -219,6 +219,29 @@ export class WidgetComponent implements OnInit {
return encodedURL;
}
/**
* Performs the templating calculation on a URL as best as it can. Variables that cannot be converted
* will be left unchanged, such as the $matrix_display_name and $matrix_avatar_url. This is intended to
* be used for Scalar compatibility operations.
* @param {string} urlTemplate The URL with variables to template
* @param {*} data The data to consider while templating
* @returns {string} The URL with the variables replaced
*/
protected templateUrl(urlTemplate: string, data: any): string {
let result = urlTemplate;
result = result.replace("$matrix_room_id", SessionStorage.roomId);
result = result.replace("$matrix_user_id", SessionStorage.userId);
// result = result.replace("$matrix_display_name", "NOT SUPPORTED");
// result = result.replace("$matrix_avatar_url", "NOT SUPPORTED");
for (const key of Object.keys(data)) {
result = result.replace("$" + key, data[key]);
}
return result;
}
/**
* Adds the widget stored in newWidget to the room.
* @returns {Promise<*>} Resolves when the widget has been added and newWidget is populated
@ -264,7 +287,7 @@ export class WidgetComponent implements OnInit {
public saveWidget(widget: EditableWidget): Promise<any> {
// Make sure we call "before add" before validating the URL
try {
this.OnWidgetBeforeEdit(this.newWidget);
this.OnWidgetBeforeEdit(widget);
} catch (error) {
this.toaster.pop("warning", error.message);
return;

View file

@ -15,7 +15,7 @@ export class DimensionApiService extends AuthedApi {
}
public getWidget(type: string): Promise<Widget> {
return this.http.get("/api/v1/dimension/widget/" + type).map(r => r.json()).toPromise();
return this.http.get("/api/v1/dimension/integrations/widget/" + type).map(r => r.json()).toPromise();
}
public isEmbeddable(url: string): Promise<any> { // 200 = success, anything else = error