Create meeting on /widget_state, deal with dead/unknown meetings
This commit is contained in:
parent
32d0bd3aec
commit
a1e12f353a
|
@ -6,9 +6,10 @@ import { BigBlueButtonJoinRequest } from "../../models/Widget";
|
|||
import { BigBlueButtonJoinResponse, BigBlueButtonCreateAndJoinMeetingResponse, BigBlueButtonWidgetResponse } from "../../models/WidgetResponses";
|
||||
import { AutoWired } from "typescript-ioc/es6";
|
||||
import { ApiError } from "../ApiError";
|
||||
import { sha1, sha256 } from "../../utils/hashing";
|
||||
import { sha256 } from "../../utils/hashing";
|
||||
import config from "../../config";
|
||||
import { parseStringPromise } from "xml2js";
|
||||
import * as randomString from "random-string";
|
||||
|
||||
/**
|
||||
* API for the BigBlueButton widget.
|
||||
|
@ -24,6 +25,9 @@ export class DimensionBigBlueButtonService {
|
|||
private authenticityTokenRegexp = new RegExp(`name="authenticity_token" value="([^"]+)".*`);
|
||||
|
||||
// join handles the request from a client to join a BigBlueButton meeting
|
||||
// via a Greenlight URL. Note that this is no longer the only way to join a
|
||||
// BigBlueButton meeting. See xxx below for the API that bypasses Greenlight
|
||||
// and instead calls the BigBlueButton API directly.
|
||||
//
|
||||
// The client is expected to send a link created by greenlight, the nice UI
|
||||
// that's recommended to be installed on top of BBB, which is itself a BBB
|
||||
|
@ -166,6 +170,217 @@ export class DimensionBigBlueButtonService {
|
|||
return {url: joinUrl};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clients can call this endpoint in order to retrieve the contents of the widget room state.
|
||||
* This endpoint will create a BigBlueButton meeting and place the returned ID and password in the room state.
|
||||
* @param {string} roomId The ID of the room that the widget will live in.
|
||||
*/
|
||||
@GET
|
||||
@Path("widget_state")
|
||||
public async widget(
|
||||
@QueryParam("roomId") roomId: string,
|
||||
): Promise<BigBlueButtonWidgetResponse|ApiError> {
|
||||
// Hash the room ID in order to generate a unique widget ID
|
||||
const widgetId = sha256(roomId + "bigbluebutton");
|
||||
|
||||
const widgetName = config.bigbluebutton.widgetName;
|
||||
const widgetTitle = config.bigbluebutton.widgetTitle;
|
||||
const widgetAvatarUrl = config.bigbluebutton.widgetAvatarUrl;
|
||||
|
||||
LogService.info("BigBlueButton", "Got a meeting create request for room: " + roomId);
|
||||
|
||||
// NOTE: BBB meetings will by default end a minute or two after the last person leaves.
|
||||
const createQueryParameters = {
|
||||
meetingID: randomString(20),
|
||||
// To help admins link meeting IDs to rooms
|
||||
meta_MatrixRoomID: roomId,
|
||||
};
|
||||
|
||||
// Create a new meeting.
|
||||
const createResponse = await this.makeBBBApiCall("GET", "create", createQueryParameters, null);
|
||||
LogService.info("BigBlueButton", createResponse);
|
||||
|
||||
// The password users will join with.
|
||||
// TODO: We could give users access to moderate the meeting if we returned createResponse.moderatorPW, but it's
|
||||
// unclear how we pass this to the user without also leaking it to others in the room.
|
||||
// We could have the client request the password and depending on their power level in the room, return either
|
||||
// the attendee or moderator one.
|
||||
const attendeePassword = createResponse.attendeePW[0];
|
||||
|
||||
// TODO: How do we get the user dimension is actually running as?
|
||||
const widgetCreatorUserId = "@dimension:" + config.homeserver.name;
|
||||
|
||||
// Add all necessary client variables to the url when loading the widget
|
||||
const widgetUrl = config.dimension.publicUrl +
|
||||
"/widgets/bigbluebutton" +
|
||||
"?widgetId=$matrix_widget_id" +
|
||||
"&roomId=$matrix_room_id" +
|
||||
"&createMeeting=true" +
|
||||
"&displayName=$matrix_display_name" +
|
||||
"&avatarUrl=$matrix_avatar_url" +
|
||||
"&userId=$matrix_user_id" +
|
||||
`&meetingId=${createResponse.meetingID[0]}` +
|
||||
`&meetingPassword=${attendeePassword}` +
|
||||
"&auth=$openidtoken-jwt";
|
||||
|
||||
return {
|
||||
"widget_id": widgetId,
|
||||
"widget": {
|
||||
"creatorUserId": widgetCreatorUserId,
|
||||
"id": widgetId,
|
||||
"type": "m.custom",
|
||||
"waitForIframeLoad": true,
|
||||
"name": widgetName,
|
||||
"avatar_url": widgetAvatarUrl,
|
||||
"url": widgetUrl,
|
||||
"data": {
|
||||
"title": widgetTitle,
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"container": "top",
|
||||
"index": 0,
|
||||
"width": 65,
|
||||
"height": 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clients can call this endpoint in order to retrieve a URL that leads to the BigBlueButton API that they can
|
||||
* use to join the meeting with. They will need to provide the meeting ID and password which are only available
|
||||
* from the widget room state event.
|
||||
* @param {string} displayName The displayname of the user.
|
||||
* @param {string} userId The Matrix User ID of the user.
|
||||
* @param {string} avatarUrl The avatar of the user (mxc://...).
|
||||
* @param {string} meetingId The meeting ID to join.
|
||||
* @param {string} password The password to attempt to join the meeting with.
|
||||
*/
|
||||
@GET
|
||||
@Path("getJoinUrl")
|
||||
public async createAndJoinMeeting(
|
||||
@QueryParam("displayName") displayName: string,
|
||||
@QueryParam("userId") userId: string,
|
||||
@QueryParam("avatarUrl") avatarUrl: string,
|
||||
@QueryParam("meetingId") meetingId: string,
|
||||
@QueryParam("meetingPassword") password: string,
|
||||
): Promise<BigBlueButtonCreateAndJoinMeetingResponse|ApiError> {
|
||||
// Check if the meeting is actually running. If not, return an error
|
||||
let isMeetingRunningParameters = {
|
||||
meetingID: meetingId,
|
||||
}
|
||||
|
||||
const isMeetingRunningResponse = await this.makeBBBApiCall("GET", "isMeetingRunning", isMeetingRunningParameters, null);
|
||||
if (isMeetingRunningResponse.running[0].toLowerCase() !== "true") {
|
||||
// This meeting is not running, inform the user
|
||||
return new ApiError(
|
||||
400,
|
||||
{error: "This meeting does not exist or has ended."},
|
||||
"UNKNOWN_MEETING_ID",
|
||||
);
|
||||
}
|
||||
|
||||
let joinQueryParameters = {
|
||||
meetingID: meetingId,
|
||||
password: password,
|
||||
fullName: `${displayName} (${userId})`,
|
||||
userID: userId,
|
||||
}
|
||||
|
||||
// Add an avatar to the join request if the user provided one
|
||||
if (avatarUrl.startsWith("mxc")) {
|
||||
joinQueryParameters["avatarURL"] = this.getHTTPAvatarUrlFromMXCUrl(avatarUrl);
|
||||
}
|
||||
|
||||
// Calculate the checksum for the join URL. We need to do so as a browser would as we're passing this back to a browser
|
||||
const checksum = this.bbbChecksumFromCallNameAndQueryParamaters("join", joinQueryParameters, true);
|
||||
|
||||
// Construct the join URL, which we'll give back to the client, who can then add additional parameters to (or we just do it)
|
||||
const url = `${config.bigbluebutton.apiBaseUrl}/join?${this.queryStringFromObject(joinQueryParameters, true)}&checksum=${checksum}`;
|
||||
|
||||
return {
|
||||
url: url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API call to the configured BBB server instance.
|
||||
* @param {string} method The HTTP method to use for the request.
|
||||
* @param {string} apiCallName The name of the API (the last bit of the endpoint) to call. e.g 'create', 'join'.
|
||||
* @param {any} queryParameters The query parameters to use in the request.
|
||||
* @param {any} body The body of the request.
|
||||
* @returns {any} The response to the call.
|
||||
*/
|
||||
private async makeBBBApiCall(
|
||||
method: string,
|
||||
apiCallName: string,
|
||||
queryParameters: any,
|
||||
body: any,
|
||||
): Promise<any> {
|
||||
// Compute the checksum needed to authenticate the request (as derived from the configured shared secret)
|
||||
queryParameters.checksum = this.bbbChecksumFromCallNameAndQueryParamaters(apiCallName, queryParameters, false);
|
||||
|
||||
// Get the URL host and path using the configured api base and the API call name
|
||||
const url = `${config.bigbluebutton.apiBaseUrl}/${apiCallName}`;
|
||||
|
||||
// Now make the request!
|
||||
const response = await this.doRequest(method, url, queryParameters, body);
|
||||
|
||||
// Parse and return the XML from the response
|
||||
// TODO: XML parsing error handling
|
||||
const parsedResponse = await parseStringPromise(response.body);
|
||||
|
||||
// Extract the "response" object
|
||||
return parsedResponse.response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object representing a query string into a checksum suitable for appending to a BBB API call.
|
||||
* Docs: https://docs.bigbluebutton.org/dev/api.html#usage
|
||||
* @param {string} apiCallName The name of the API to call, e.g "create", "join".
|
||||
* @param {any} queryParameters An object representing a set of query parameters represented by keys and values.
|
||||
* @param {boolean} encodeAsBrowser Whether to encode the query string as a browser would.
|
||||
* @returns {string} The checksum for the request.
|
||||
*/
|
||||
private bbbChecksumFromCallNameAndQueryParamaters(apiCallName: string, queryParameters: any, encodeAsBrowser: boolean): string {
|
||||
// Convert the query parameters object into a string
|
||||
// We URL encode each value as a browser would. If we don't, our resulting checksum will not match.
|
||||
const widgetQueryString = this.queryStringFromObject(queryParameters, encodeAsBrowser);
|
||||
|
||||
LogService.info("BigBlueButton", "Built widget string:" + widgetQueryString);
|
||||
LogService.info("BigBlueButton", "Hashing:" + apiCallName + widgetQueryString + config.bigbluebutton.sharedSecret);
|
||||
|
||||
// Hash the api name and query parameters to get the checksum, and add it to the set of query parameters
|
||||
return sha256(apiCallName + widgetQueryString + config.bigbluebutton.sharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object containing keys and values as strings into a string representing URL query parameters.
|
||||
* @param queryParameters
|
||||
* @param encodeAsBrowser
|
||||
* @returns {string} The query parameter object as a string.
|
||||
*/
|
||||
private queryStringFromObject(queryParameters: any, encodeAsBrowser: boolean): string {
|
||||
return Object.keys(queryParameters).map(k => k + "=" + this.encodeForUrl(queryParameters[k], encodeAsBrowser)).join("&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string in the same fashion browsers do (encoding ! and other characters).
|
||||
* @param {string} text The text to encode.
|
||||
* @param {boolean} encodeAsBrowser Whether to encode the query string as a browser would.
|
||||
* @returns {string} The encoded text.
|
||||
*/
|
||||
private encodeForUrl(text: string, encodeAsBrowser: boolean): string {
|
||||
let encodedText = encodeURIComponent(text);
|
||||
if (!encodeAsBrowser) {
|
||||
// use + instead of %20 for space to match what the 'request' JavaScript library does do.
|
||||
// encodeURIComponent doesn't escape !'()*, so manually escape them.
|
||||
encodedText = encodedText.replace(/%20/g, '+').replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
}
|
||||
|
||||
return encodedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an HTTP request.
|
||||
* @param {string} method The HTTP method to use.
|
||||
|
@ -216,171 +431,13 @@ export class DimensionBigBlueButtonService {
|
|||
});
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("widget_state")
|
||||
public async widget(
|
||||
@QueryParam("room_id") roomId: string,
|
||||
): Promise<BigBlueButtonWidgetResponse|ApiError> {
|
||||
// Hash the room ID in order to generate a unique widget ID
|
||||
const widgetId = sha256(roomId + "bigbluebutton");
|
||||
private getHTTPAvatarUrlFromMXCUrl(mxc: string): string {
|
||||
const width = 64;
|
||||
const height = 64;
|
||||
const method = "scale";
|
||||
|
||||
const widgetName = config.bigbluebutton.widgetName;
|
||||
const widgetTitle = config.bigbluebutton.widgetTitle;
|
||||
const widgetAvatarUrl = config.bigbluebutton.widgetAvatarUrl;
|
||||
|
||||
// TODO: What should we put for the creatorUserId? Also make it configurable?
|
||||
const widgetCreatorUserId = "@bbb:localhost";
|
||||
|
||||
// Add all necessary client variables to the url when loading the widget
|
||||
const widgetUrl = config.dimension.publicUrl +
|
||||
"/widgets/bigbluebutton" +
|
||||
"?widgetId=$matrix_widget_id&roomId=$matrix_room_id&createMeeting=true&displayName=$matrix_display_name&avatarUrl=$matrix_avatar_url&userId=$matrix_user_id&auth=openidtoken-jwt";
|
||||
|
||||
return {
|
||||
"widget_id": widgetId,
|
||||
"widget": {
|
||||
"creatorUserId": widgetCreatorUserId,
|
||||
"id": widgetId,
|
||||
"type": "m.custom",
|
||||
"waitForIframeLoad": true,
|
||||
"name": widgetName,
|
||||
"avatar_url": widgetAvatarUrl,
|
||||
"url": widgetUrl,
|
||||
"data": {
|
||||
"title": widgetTitle,
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"container": "top",
|
||||
"index": 0,
|
||||
"width": 65,
|
||||
"height": 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("create")
|
||||
public async createAndJoinMeeting(
|
||||
@QueryParam("roomId") roomId: string,
|
||||
@QueryParam("fullName") fullName: string,
|
||||
): Promise<BigBlueButtonCreateAndJoinMeetingResponse|ApiError> {
|
||||
// Check if a meeting already exists for this room...
|
||||
LogService.info("BigBlueButton", "Got a meeting create and join request for room: " + roomId);
|
||||
|
||||
// Create a new meeting
|
||||
LogService.info("BigBlueButton", "Using secret: " + config.bigbluebutton.sharedSecret);
|
||||
|
||||
// NOTE: BBB meetings will by default end a minute or two after the last person leaves.
|
||||
const createQueryParameters = {
|
||||
meetingID: roomId + "bigbluebuttondimension",
|
||||
attendeePW: "a",
|
||||
moderatorPW: "b",
|
||||
};
|
||||
|
||||
// TODO: Contrary to the documentation, one needs to provide a meeting ID, attendee and moderator password in order
|
||||
// for creating meeting to be idempotent. For now we use dummy passwords, though we may want to consider generating
|
||||
// some once we actually start authenticating meetings.
|
||||
const createResponse = await this.makeBBBApiCall("GET", "create", createQueryParameters, null);
|
||||
LogService.info("BigBlueButton", createResponse);
|
||||
|
||||
// Grab the meeting ID and password from the create response
|
||||
const returnedMeetingId = createResponse.meetingID[0];
|
||||
const returnedAttendeePassword = createResponse.attendeePW[0];
|
||||
const joinQueryParameters = {
|
||||
meetingID: returnedMeetingId,
|
||||
password: returnedAttendeePassword,
|
||||
fullName: fullName,
|
||||
}
|
||||
|
||||
// Calculate the checksum for the join URL. We need to do so as a browser would as we're passing this back to a browser
|
||||
const checksum = this.bbbChecksumFromCallNameAndQueryParamaters("join", joinQueryParameters, true);
|
||||
|
||||
// Construct the join URL, which we'll give back to the client, who can then add additional parameters to (or we just do it)
|
||||
const url = `${config.bigbluebutton.apiBaseUrl}/join?${this.queryStringFromObject(joinQueryParameters, true)}&checksum=${checksum}`;
|
||||
|
||||
return {
|
||||
url: url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API call to the configured BBB server instance
|
||||
* @param {string} method The HTTP method to use for the request.
|
||||
* @param {string} apiCallName The name of the API (the last bit of the endpoint) to call. e.g 'create', 'join'.
|
||||
* @param {any} queryParameters The query parameters to use in the request.
|
||||
* @param {any} body The body of the request.
|
||||
* @returns {BigBlueButtonApiResponse} The response to the call.
|
||||
*/
|
||||
private async makeBBBApiCall(
|
||||
method: string,
|
||||
apiCallName: string,
|
||||
queryParameters: any,
|
||||
body: any,
|
||||
): Promise<any> {
|
||||
// Compute the checksum needed to authenticate the request (as derived from the configured shared secret)
|
||||
queryParameters.checksum = this.bbbChecksumFromCallNameAndQueryParamaters(apiCallName, queryParameters, false);
|
||||
|
||||
// Get the URL host and path using the configured api base and the API call name
|
||||
const url = `${config.bigbluebutton.apiBaseUrl}/${apiCallName}`;
|
||||
|
||||
// Now make the request!
|
||||
const response = await this.doRequest(method, url, queryParameters, body);
|
||||
|
||||
// Parse and return the XML from the response
|
||||
// TODO: XML parsing error handling
|
||||
const parsedResponse = await parseStringPromise(response.body);
|
||||
|
||||
// Extract the "response" object
|
||||
return parsedResponse.response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object representing a query string into a checksum suitable for appending to a BBB API call.
|
||||
* Docs: https://docs.bigbluebutton.org/dev/api.html#usage
|
||||
* @param {string} apiCallName The name of the API to call, e.g "create", "join".
|
||||
* @param {any} queryParameters An object representing a set of query parameters represented by keys and values.
|
||||
* @param {boolean} encodeAsBrowser Whether to encode the query string as a browser would.
|
||||
* @returns {string} The checksum for the request.
|
||||
*/
|
||||
private bbbChecksumFromCallNameAndQueryParamaters(apiCallName: string, queryParameters: any, encodeAsBrowser: boolean): string {
|
||||
// Convert the query parameters object into a string
|
||||
// We URL encode each value as a browser would. If we don't, our resulting checksum will not match.
|
||||
const widgetQueryString = this.queryStringFromObject(queryParameters, encodeAsBrowser);
|
||||
|
||||
LogService.info("BigBlueButton", "Built widget string:" + widgetQueryString);
|
||||
LogService.info("BigBlueButton", "Hashing:" + apiCallName + widgetQueryString + config.bigbluebutton.sharedSecret);
|
||||
|
||||
// SHA1 hash the api name and query parameters to get the checksum, and add it to the set of query parameters
|
||||
// TODO: Try Sha256
|
||||
return sha1(apiCallName + widgetQueryString + config.bigbluebutton.sharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* A
|
||||
* @param queryParameters
|
||||
* @param encodeAsBrowser
|
||||
* @private
|
||||
*/
|
||||
private queryStringFromObject(queryParameters: any, encodeAsBrowser: boolean): string {
|
||||
return Object.keys(queryParameters).map(k => k + "=" + this.encodeForUrl(queryParameters[k], encodeAsBrowser)).join("&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string in the same fashion browsers do (encoding ! and other characters).
|
||||
* @param {string} text The text to encode.
|
||||
* @param {boolean} encodeAsBrowser Whether to encode the query string as a browser would.
|
||||
* @returns {string} The encoded text.
|
||||
*/
|
||||
private encodeForUrl(text: string, encodeAsBrowser: boolean): string {
|
||||
let encodedText = encodeURIComponent(text);
|
||||
if (!encodeAsBrowser) {
|
||||
// use + instead of %20 for space to match what the 'request' JavaScript library does do.
|
||||
// encodeURIComponent doesn't escape !'()*, so manually escape them.
|
||||
encodedText = encodedText.replace(/%20/g, '+').replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
}
|
||||
|
||||
return encodedText;
|
||||
mxc = mxc.substring("mxc://".length).split('?')[0];
|
||||
return `${config.dimension.publicUrl}/api/v1/dimension/media/thumbnail/${mxc}?width=${width}&height=${height}&method=${method}&animated=false`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@ export function md5(text: string): string {
|
|||
return crypto.createHash("md5").update(text).digest('hex').toLowerCase();
|
||||
}
|
||||
|
||||
export function sha1(text: string): string {
|
||||
return crypto.createHash("sha1").update(text).digest('hex').toLowerCase();
|
||||
}
|
||||
|
||||
export function sha256(text: string): string {
|
||||
return crypto.createHash("sha256").update(text).digest('hex').toLowerCase();
|
||||
}
|
||||
|
|
|
@ -14,8 +14,11 @@ export class BigBlueButtonApiService extends AuthedApi {
|
|||
return this.authedGet<FE_BigBlueButtonJoin|ApiError>("/api/v1/dimension/bigbluebutton/join", {greenlightUrl: url, fullName: name}).toPromise();
|
||||
}
|
||||
|
||||
public createAndJoinMeeting(roomId: string, name: string): Promise<FE_BigBlueButtonCreateAndJoinMeeting|ApiError> {
|
||||
return this.authedGet<FE_BigBlueButtonCreateAndJoinMeeting|ApiError>("/api/v1/dimension/bigbluebutton/create", {roomId: roomId, fullName: name}).toPromise();
|
||||
public getJoinUrl(displayName: string, userId: string, avatarUrl: string, meetingId: string, meetingPassword: string): Promise<FE_BigBlueButtonCreateAndJoinMeeting|ApiError> {
|
||||
return this.authedGet<FE_BigBlueButtonCreateAndJoinMeeting|ApiError>(
|
||||
"/api/v1/dimension/bigbluebutton/getJoinUrl",
|
||||
{displayName: displayName, userId: userId, avatarUrl: avatarUrl, meetingId: meetingId, meetingPassword: meetingPassword},
|
||||
).toPromise();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { WidgetApiService } from "../../shared/services/integrations/widget-api.service";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
|
||||
import { CapableWidget } from "../capable-widget";
|
||||
|
@ -24,6 +23,9 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
private conferenceUrl: string;
|
||||
private displayName: string;
|
||||
private userId: string;
|
||||
private avatarUrl: string;
|
||||
private meetingId: string;
|
||||
private meetingPassword: string;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -71,7 +73,6 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
|
||||
constructor(activatedRoute: ActivatedRoute,
|
||||
private bigBlueButtonApi: BigBlueButtonApiService,
|
||||
private widgetApi: WidgetApiService,
|
||||
private sanitizer: DomSanitizer,
|
||||
public translate: TranslateService) {
|
||||
super();
|
||||
|
@ -83,14 +84,14 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
this.createMeeting = params.createMeeting;
|
||||
this.conferenceUrl = params.conferenceUrl;
|
||||
this.displayName = params.displayName;
|
||||
this.avatarUrl = params.avatarUrl;
|
||||
this.meetingId = params.meetingId;
|
||||
this.meetingPassword = params.meetingPassword;
|
||||
this.userId = params.userId || params.email; // Element uses `email` when placing a conference call
|
||||
|
||||
// Create a nick to display in the meeting
|
||||
this.joinName = `${this.displayName} (${this.userId})`;
|
||||
|
||||
// TODO: As of BigBlueButton 2.3, Avatar URLs are supported in /join, which would allow us to set the
|
||||
// user's avatar in BigBlueButton to that of their Matrix ID.
|
||||
|
||||
console.log("BigBlueButton: should create meeting: " + this.createMeeting);
|
||||
console.log("BigBlueButton: will join as: " + this.joinName);
|
||||
console.log("BigBlueButton: got room ID: " + this.roomId);
|
||||
|
@ -132,8 +133,8 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
|
||||
// Make a request to Dimension requesting the join URL
|
||||
if (this.createMeeting) {
|
||||
// Ask Dimension to create the meeting for us and return the URL
|
||||
this.createAndJoinMeeting();
|
||||
// Ask Dimension to return a URL for joining a meeting that it created
|
||||
this.joinThroughDimension();
|
||||
} else {
|
||||
// Provide Dimension with a Greenlight URL, which it will transform into
|
||||
// a BBB meeting URL
|
||||
|
@ -142,20 +143,20 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
}
|
||||
|
||||
// Ask Dimension to create a meeting (or use an existing one) for this room and return the embeddable meeting URL
|
||||
private createAndJoinMeeting() {
|
||||
console.log("BigBlueButton: joining and creating meeting if it doesn't already exist, with fullname:", this.joinName);
|
||||
private async joinThroughDimension() {
|
||||
console.log("BigBlueButton: Joining meeting created by Dimension with meeting ID: " + this.meetingId);
|
||||
|
||||
this.bigBlueButtonApi.createAndJoinMeeting(this.roomId, this.joinName).then((response) => {
|
||||
this.bigBlueButtonApi.getJoinUrl(this.displayName, this.userId, this.avatarUrl, this.meetingId, this.meetingPassword).then((response) => {
|
||||
console.log("The response");
|
||||
console.log(response);
|
||||
if ("errorCode" in response) {
|
||||
// This is an instance of ApiError
|
||||
// if (response.errorCode === "WAITING_FOR_MEETING_START") {
|
||||
// // The meeting hasn't started yet
|
||||
// this.statusMessage = "Waiting for conference to start...";
|
||||
//
|
||||
// // Poll until it has
|
||||
// setTimeout(this.joinConference.bind(this), this.pollIntervalMillis, false);
|
||||
// return;
|
||||
// }
|
||||
if (response.errorCode === "UNKNOWN_MEETING_ID") {
|
||||
// It's likely that everyone has left the meeting, and it's been garbage collected.
|
||||
// Inform the user that they should try and start a new meeting
|
||||
this.statusMessage = "This meeting has ended or otherwise does not exist.<br>Please start a new meeting.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise this is a generic error
|
||||
this.statusMessage = "An error occurred while loading the meeting";
|
||||
|
@ -193,25 +194,15 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
}
|
||||
|
||||
private embedMeetingWithUrl(url: string) {
|
||||
this.canEmbed = true;
|
||||
// Hide widget-related UI
|
||||
this.statusMessage = null;
|
||||
|
||||
// Embed the return meeting URL, joining the meeting
|
||||
this.canEmbed = true;
|
||||
this.embedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
return
|
||||
// Check if the given URL is embeddable
|
||||
this.widgetApi.isEmbeddable(url).then(result => {
|
||||
this.canEmbed = result.canEmbed;
|
||||
this.statusMessage = null;
|
||||
|
||||
// Embed the return meeting URL, joining the meeting
|
||||
this.embedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
||||
|
||||
// Inform the client that we would like the meeting to remain visible for its duration
|
||||
ScalarWidgetApi.sendSetAlwaysOnScreen(true);
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
this.canEmbed = false;
|
||||
this.statusMessage = "Unable to embed meeting";
|
||||
});
|
||||
// Inform the client that we would like the meeting to remain visible for its duration
|
||||
ScalarWidgetApi.sendSetAlwaysOnScreen(true);
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
|
@ -220,6 +211,8 @@ export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implement
|
|||
|
||||
protected onCapabilitiesSent(): void {
|
||||
super.onCapabilitiesSent();
|
||||
|
||||
// Don't set alwaysOnScreen until we start a meeting
|
||||
ScalarWidgetApi.sendSetAlwaysOnScreen(false);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue