[NEW] Beta support for Big Blue Button video conferencing system (#11837)
parent
3a487f930f
commit
b29af81993
@ -0,0 +1,11 @@ |
||||
Package.describe({ |
||||
name: 'rocketchat:bigbluebutton', |
||||
version: '0.0.1', |
||||
summary: 'Rocket.Chat big blue button implementation', |
||||
git: '', |
||||
}); |
||||
|
||||
Package.onUse(function(api) { |
||||
api.use('ecmascript'); |
||||
api.mainModule('server/bigbluebutton-api.js', 'server'); |
||||
}); |
@ -0,0 +1,197 @@ |
||||
/* eslint-disable */ |
||||
import crypto from 'crypto'; |
||||
|
||||
var BigBlueButtonApi, filterCustomParameters, include, noChecksumMethods, root, |
||||
__indexOf = [].indexOf || function (item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; |
||||
|
||||
BigBlueButtonApi = (function () { |
||||
function BigBlueButtonApi(url, salt, debug, opts) { |
||||
var _base; |
||||
if (debug == null) { |
||||
debug = false; |
||||
} |
||||
if (opts == null) { |
||||
opts = {}; |
||||
} |
||||
this.url = url; |
||||
this.salt = salt; |
||||
this.debug = debug; |
||||
this.opts = opts; |
||||
if ((_base = this.opts).shaType == null) { |
||||
_base.shaType = 'sha1'; |
||||
} |
||||
} |
||||
|
||||
BigBlueButtonApi.prototype.availableApiCalls = function () { |
||||
return ['/', 'create', 'join', 'isMeetingRunning', 'getMeetingInfo', 'end', 'getMeetings', 'getDefaultConfigXML', 'setConfigXML', 'enter', 'configXML', 'signOut', 'getRecordings', 'publishRecordings', 'deleteRecordings', 'updateRecordings', 'hooks/create']; |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.urlParamsFor = function (param) { |
||||
switch (param) { |
||||
case "create": |
||||
return [["meetingID", true], ["name", true], ["attendeePW", false], ["moderatorPW", false], ["welcome", false], ["dialNumber", false], ["voiceBridge", false], ["webVoice", false], ["logoutURL", false], ["maxParticipants", false], ["record", false], ["duration", false], ["moderatorOnlyMessage", false], ["autoStartRecording", false], ["allowStartStopRecording", false], [/meta_\w+/, false]]; |
||||
case "join": |
||||
return [["fullName", true], ["meetingID", true], ["password", true], ["createTime", false], ["userID", false], ["webVoiceConf", false], ["configToken", false], ["avatarURL", false], ["redirect", false], ["clientURL", false]]; |
||||
case "isMeetingRunning": |
||||
return [["meetingID", true]]; |
||||
case "end": |
||||
return [["meetingID", true], ["password", true]]; |
||||
case "getMeetingInfo": |
||||
return [["meetingID", true], ["password", true]]; |
||||
case "getRecordings": |
||||
return [["meetingID", false], ["recordID", false], ["state", false], [/meta_\w+/, false]]; |
||||
case "publishRecordings": |
||||
return [["recordID", true], ["publish", true]]; |
||||
case "deleteRecordings": |
||||
return [["recordID", true]]; |
||||
case "updateRecordings": |
||||
return [["recordID", true], [/meta_\w+/, false]]; |
||||
case "hooks/create": |
||||
return [["callbackURL", false], ["meetingID", false]]; |
||||
} |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.filterParams = function (params, method) { |
||||
var filters, r; |
||||
filters = this.urlParamsFor(method); |
||||
if ((filters == null) || filters.length === 0) { |
||||
({}); |
||||
} else { |
||||
r = include(params, function (key, value) { |
||||
var filter, _i, _len; |
||||
for (_i = 0, _len = filters.length; _i < _len; _i++) { |
||||
filter = filters[_i]; |
||||
if (filter[0] instanceof RegExp) { |
||||
if (key.match(filter[0]) || key.match(/^custom_/)) { |
||||
return true; |
||||
} |
||||
} else { |
||||
if (key.match("^" + filter[0] + "$") || key.match(/^custom_/)) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
return filterCustomParameters(r); |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.urlFor = function (method, params, filter) { |
||||
var checksum, key, keys, param, paramList, property, query, sep, url, _i, _len; |
||||
if (filter == null) { |
||||
filter = true; |
||||
} |
||||
if (this.debug) { |
||||
console.log("Generating URL for", method); |
||||
} |
||||
if (filter) { |
||||
params = this.filterParams(params, method); |
||||
} else { |
||||
params = filterCustomParameters(params); |
||||
} |
||||
url = this.url; |
||||
paramList = []; |
||||
if (params != null) { |
||||
keys = []; |
||||
for (property in params) { |
||||
keys.push(property); |
||||
} |
||||
keys = keys.sort(); |
||||
for (_i = 0, _len = keys.length; _i < _len; _i++) { |
||||
key = keys[_i]; |
||||
if (key != null) { |
||||
param = params[key]; |
||||
} |
||||
if (param != null) { |
||||
paramList.push("" + (this.encodeForUrl(key)) + "=" + (this.encodeForUrl(param))); |
||||
} |
||||
} |
||||
if (paramList.length > 0) { |
||||
query = paramList.join("&"); |
||||
} |
||||
} else { |
||||
query = ''; |
||||
} |
||||
checksum = this.checksum(method, query); |
||||
if (paramList.length > 0) { |
||||
query = "" + method + "?" + query; |
||||
sep = '&'; |
||||
} else { |
||||
if (method !== '/') { |
||||
query = method; |
||||
} |
||||
sep = '?'; |
||||
} |
||||
if (__indexOf.call(noChecksumMethods(), method) < 0) { |
||||
query = "" + query + sep + "checksum=" + checksum; |
||||
} |
||||
return "" + url + "/" + query; |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.checksum = function (method, query) { |
||||
var c, shaObj, str; |
||||
query || (query = ""); |
||||
if (this.debug) { |
||||
console.log("- Calculating the checksum using: '" + method + "', '" + query + "', '" + this.salt + "'"); |
||||
} |
||||
str = method + query + this.salt; |
||||
if (this.opts.shaType === 'sha256') { |
||||
shaObj = crypto.createHash('sha256', "TEXT") |
||||
} else { |
||||
shaObj = crypto.createHash('sha1', "TEXT") |
||||
} |
||||
shaObj.update(str); |
||||
c = shaObj.digest('hex'); |
||||
if (this.debug) { |
||||
console.log("- Checksum calculated:", c); |
||||
} |
||||
return c; |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.encodeForUrl = function (value) { |
||||
return encodeURIComponent(value).replace(/%20/g, '+').replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); |
||||
}; |
||||
|
||||
BigBlueButtonApi.prototype.setMobileProtocol = function (url) { |
||||
return url.replace(/http[s]?\:\/\//, "bigbluebutton://"); |
||||
}; |
||||
|
||||
return BigBlueButtonApi; |
||||
|
||||
})(); |
||||
|
||||
include = function (input, _function) { |
||||
var key, value, _match, _obj; |
||||
_obj = new Object; |
||||
_match = null; |
||||
for (key in input) { |
||||
value = input[key]; |
||||
if (_function.call(input, key, value)) { |
||||
_obj[key] = value; |
||||
} |
||||
} |
||||
return _obj; |
||||
}; |
||||
|
||||
export default BigBlueButtonApi; |
||||
|
||||
filterCustomParameters = function (params) { |
||||
var key, v; |
||||
for (key in params) { |
||||
v = params[key]; |
||||
if (key.match(/^custom_/)) { |
||||
params[key.replace(/^custom_/, "")] = v; |
||||
} |
||||
} |
||||
for (key in params) { |
||||
if (key.match(/^custom_/)) { |
||||
delete params[key]; |
||||
} |
||||
} |
||||
return params; |
||||
}; |
||||
|
||||
noChecksumMethods = function () { |
||||
return ['setConfigXML', '/', 'enter', 'configXML', 'signOut']; |
||||
}; |
@ -1,9 +1,17 @@ |
||||
RocketChat.saveStreamingOptions = function(rid, streamingOptions) { |
||||
RocketChat.saveStreamingOptions = function(rid, options) { |
||||
if (!Match.test(rid, String)) { |
||||
throw new Meteor.Error('invalid-room', 'Invalid room', { |
||||
function: 'RocketChat.saveStreamingOptions', |
||||
}); |
||||
} |
||||
|
||||
return RocketChat.models.Rooms.setStreamingOptionsById(rid, streamingOptions); |
||||
check(options, { |
||||
type: Match.Optional(String), |
||||
url: Match.Optional(String), |
||||
thumbnail: Match.Optional(String), |
||||
isAudioOnly: Match.Optional(String), |
||||
message: Match.Optional(String), |
||||
}); |
||||
|
||||
RocketChat.models.Rooms.setStreamingOptionsById(rid, options); |
||||
}; |
||||
|
@ -0,0 +1,3 @@ |
||||
<template name="bbbLiveView"> |
||||
<iframe allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" allow="geolocation; microphone; camera" src="{{source}}" width="380" height="400" frameborder="0"></iframe> |
||||
</template> |
@ -0,0 +1,16 @@ |
||||
<template name="videoFlexTabBbb"> |
||||
<div class="rc-user-info__flex rc-user-info__row"> |
||||
{{#if live}} |
||||
<button type="button" class="rc-button rc-button--primary js-join-meeting">{{_ "Join Meeting"}}</button> |
||||
{{#if callManagement}} |
||||
<button type="button" class="rc-button rc-button--cancel js-end-meeting">{{_ "End Meeting"}}</button> |
||||
{{/if}} |
||||
{{else}} |
||||
{{#if callManagement}} |
||||
<button type="button" class="rc-button rc-button--primary js-join-meeting">{{_ "Start Meeting"}}</button> |
||||
{{else}} |
||||
<p>{{_ "You have no permission to start a call"}}</p> |
||||
{{/if}} |
||||
{{/if}} |
||||
</div> |
||||
</template> |
@ -0,0 +1,63 @@ |
||||
/* eslint new-cap: [2, {"capIsNewExceptions": ["MD5"]}] */ |
||||
/* globals popout */ |
||||
|
||||
Template.videoFlexTabBbb.helpers({ |
||||
openInNewWindow() { |
||||
if (Meteor.isCordova) { |
||||
return true; |
||||
} else { |
||||
return RocketChat.settings.get('Jitsi_Open_New_Window'); |
||||
} |
||||
}, |
||||
|
||||
live() { |
||||
const isLive = RocketChat.models.Rooms.findOne({ _id: this.rid, 'streamingOptions.type': 'call' }, { fields: { streamingOptions: 1 } }) != null; |
||||
|
||||
if (isLive === false && popout.context) { |
||||
popout.close(); |
||||
} |
||||
|
||||
return isLive; |
||||
}, |
||||
|
||||
callManagement() { |
||||
const type = RocketChat.models.Rooms.findOne({ _id: this.rid }).t; |
||||
return type === 'd' || RocketChat.authz.hasAllPermission('call-management') || RocketChat.authz.hasAllPermission('call-management', this.rid); |
||||
}, |
||||
}); |
||||
|
||||
Template.videoFlexTabBbb.onCreated(function() { |
||||
this.tabBar = Template.currentData().tabBar; |
||||
}); |
||||
|
||||
Template.videoFlexTabBbb.events({ |
||||
'click .js-join-meeting'(e) { |
||||
$(e.currentTarget).prop('disabled', true); |
||||
Meteor.call('bbbJoin', { rid: this.rid }, (err, result) => { |
||||
$(e.currentTarget).prop('disabled', false); |
||||
console.log(err, result); |
||||
if (result) { |
||||
popout.open({ |
||||
content: 'bbbLiveView', |
||||
data: { |
||||
source: result.url, |
||||
streamingOptions: result, |
||||
canOpenExternal: true, |
||||
showVideoControls: false, |
||||
}, |
||||
onCloseCallback: () => console.log('bye popout'), |
||||
}); |
||||
} |
||||
}); |
||||
// Get the link and open the iframe
|
||||
}, |
||||
|
||||
'click .js-end-meeting'(e) { |
||||
$(e.currentTarget).prop('disabled', true); |
||||
Meteor.call('bbbEnd', { rid: this.rid }, (err, result) => { |
||||
// $(e.currentTarget).prop('disabled', false);
|
||||
console.log(err, result); |
||||
}); |
||||
// Get the link and open the iframe
|
||||
}, |
||||
}); |
@ -0,0 +1,158 @@ |
||||
import BigBlueButtonApi from 'meteor/rocketchat:bigbluebutton'; |
||||
import { HTTP } from 'meteor/http'; |
||||
import xml2js from 'xml2js'; |
||||
|
||||
const parser = new xml2js.Parser({ |
||||
explicitRoot: true, |
||||
}); |
||||
|
||||
const parseString = Meteor.wrapAsync(parser.parseString); |
||||
|
||||
const getBBBAPI = () => { |
||||
const url = RocketChat.settings.get('bigbluebutton_server'); |
||||
const secret = RocketChat.settings.get('bigbluebutton_sharedSecret'); |
||||
const api = new BigBlueButtonApi(`${ url }/bigbluebutton/api`, secret); |
||||
|
||||
return { api, url }; |
||||
}; |
||||
|
||||
Meteor.methods({ |
||||
bbbJoin({ rid }) { |
||||
|
||||
if (!this.userId) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'bbbJoin' }); |
||||
} |
||||
|
||||
if (!Meteor.call('canAccessRoom', rid, this.userId)) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'bbbJoin' }); |
||||
} |
||||
|
||||
if (!RocketChat.settings.get('bigbluebutton_Enabled')) { |
||||
throw new Meteor.Error('error-not-allowed', 'Not Allowed', { method: 'bbbJoin' }); |
||||
} |
||||
|
||||
const { api, url } = getBBBAPI(); |
||||
const meetingID = RocketChat.settings.get('uniqueID') + rid; |
||||
const room = RocketChat.models.Rooms.findOneById(rid); |
||||
const createUrl = api.urlFor('create', { |
||||
name: room.t === 'd' ? 'Direct' : room.name, |
||||
meetingID, |
||||
attendeePW: 'ap', |
||||
moderatorPW: 'mp', |
||||
welcome: '<br>Welcome to <b>%%CONFNAME%%</b>!', |
||||
meta_html5chat: false, |
||||
meta_html5navbar: false, |
||||
meta_html5autoswaplayout: true, |
||||
meta_html5autosharewebcam: false, |
||||
meta_html5hidepresentation: true, |
||||
}); |
||||
|
||||
const createResult = HTTP.get(createUrl); |
||||
const doc = parseString(createResult.content); |
||||
|
||||
if (doc.response.returncode[0]) { |
||||
const user = RocketChat.models.Users.findOneById(this.userId); |
||||
|
||||
const hookApi = api.urlFor('hooks/create', { |
||||
meetingID, |
||||
callbackURL: Meteor.absoluteUrl(`api/v1/videoconference.bbb.update/${ meetingID }`), |
||||
}); |
||||
|
||||
const hookResult = HTTP.get(hookApi); |
||||
|
||||
if (hookResult.statusCode !== 200) { |
||||
// TODO improve error logging
|
||||
console.log({ hookResult }); |
||||
return; |
||||
} |
||||
|
||||
RocketChat.saveStreamingOptions(rid, { |
||||
type: 'call', |
||||
}); |
||||
|
||||
return { |
||||
url: api.urlFor('join', { |
||||
password: 'mp', // mp if moderator ap if attendee
|
||||
meetingID, |
||||
fullName: user.username, |
||||
userID: user._id, |
||||
avatarURL: Meteor.absoluteUrl(`avatar/${ user.username }`), |
||||
clientURL: `${ url }/html5client/join`, |
||||
}), |
||||
}; |
||||
} |
||||
}, |
||||
|
||||
bbbEnd({ rid }) { |
||||
if (!this.userId) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'bbbEnd' }); |
||||
} |
||||
|
||||
if (!Meteor.call('canAccessRoom', rid, this.userId)) { |
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'bbbEnd' }); |
||||
} |
||||
|
||||
if (!RocketChat.settings.get('bigbluebutton_Enabled')) { |
||||
throw new Meteor.Error('error-not-allowed', 'Not Allowed', { method: 'bbbEnd' }); |
||||
} |
||||
|
||||
const { api } = getBBBAPI(); |
||||
const meetingID = RocketChat.settings.get('uniqueID') + rid; |
||||
const endApi = api.urlFor('end', { |
||||
meetingID, |
||||
password: 'mp', // mp if moderator ap if attendee
|
||||
}); |
||||
|
||||
const endApiResult = HTTP.get(endApi); |
||||
|
||||
if (endApiResult.statusCode !== 200) { |
||||
// TODO improve error logging
|
||||
console.log({ endApiResult }); |
||||
return; |
||||
} |
||||
|
||||
const doc = parseString(endApiResult.content); |
||||
|
||||
if (doc.response.returncode[0] === 'FAILED') { |
||||
RocketChat.saveStreamingOptions(rid, {}); |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
RocketChat.API.v1.addRoute('videoconference.bbb.update/:id', { authRequired: false }, { |
||||
post() { |
||||
// TODO check checksum
|
||||
const event = JSON.parse(this.bodyParams.event)[0]; |
||||
const eventType = event.data.id; |
||||
const meetingID = event.data.attributes.meeting['external-meeting-id']; |
||||
const rid = meetingID.replace(RocketChat.settings.get('uniqueID'), ''); |
||||
|
||||
console.log(eventType, rid); |
||||
|
||||
if (eventType === 'meeting-ended') { |
||||
RocketChat.saveStreamingOptions(rid, {}); |
||||
} |
||||
|
||||
// if (eventType === 'user-left') {
|
||||
// const { api } = getBBBAPI();
|
||||
|
||||
// const getMeetingInfoApi = api.urlFor('getMeetingInfo', {
|
||||
// meetingID
|
||||
// });
|
||||
|
||||
// const getMeetingInfoResult = HTTP.get(getMeetingInfoApi);
|
||||
|
||||
// if (getMeetingInfoResult.statusCode !== 200) {
|
||||
// // TODO improve error logging
|
||||
// console.log({ getMeetingInfoResult });
|
||||
// }
|
||||
|
||||
// const doc = parseString(getMeetingInfoResult.content);
|
||||
|
||||
// if (doc.response.returncode[0]) {
|
||||
// const participantCount = parseInt(doc.response.participantCount[0]);
|
||||
// console.log(participantCount);
|
||||
// }
|
||||
// }
|
||||
}, |
||||
}); |
@ -1,70 +1,132 @@ |
||||
Meteor.startup(function() { |
||||
RocketChat.settings.addGroup('Video Conference', function() { |
||||
this.add('Jitsi_Enabled', false, { |
||||
type: 'boolean', |
||||
i18nLabel: 'Enabled', |
||||
alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Domain', 'meet.jit.si', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Domain', |
||||
public: true, |
||||
}); |
||||
this.section('BigBlueButton', function() { |
||||
|
||||
this.add('Jitsi_URL_Room_Prefix', 'RocketChat', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'URL_room_prefix', |
||||
public: true, |
||||
}); |
||||
this.add('bigbluebutton_Enabled', false, { |
||||
type: 'boolean', |
||||
i18nLabel: 'Enabled', |
||||
alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_SSL', true, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'SSL', |
||||
public: true, |
||||
}); |
||||
this.add('bigbluebutton_server', '', { |
||||
type: 'string', |
||||
i18nLabel: 'Domain', |
||||
enableQuery: { |
||||
_id: 'bigbluebutton_Enabled', |
||||
value: true, |
||||
}, |
||||
}); |
||||
|
||||
this.add('Jitsi_Open_New_Window', false, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Always_open_in_new_window', |
||||
public: true, |
||||
}); |
||||
this.add('bigbluebutton_sharedSecret', '', { |
||||
type: 'string', |
||||
i18nLabel: 'Shared_Secret', |
||||
enableQuery: { |
||||
_id: 'bigbluebutton_Enabled', |
||||
value: true, |
||||
}, |
||||
}); |
||||
|
||||
this.add('bigbluebutton_enable_d', true, { |
||||
type: 'boolean', |
||||
i18nLabel: 'WebRTC_Enable_Direct', |
||||
enableQuery: { |
||||
_id: 'bigbluebutton_Enabled', |
||||
value: true, |
||||
}, |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('bigbluebutton_enable_p', true, { |
||||
type: 'boolean', |
||||
i18nLabel: 'WebRTC_Enable_Private', |
||||
enableQuery: { |
||||
_id: 'bigbluebutton_Enabled', |
||||
value: true, |
||||
}, |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('bigbluebutton_enable_c', false, { |
||||
type: 'boolean', |
||||
i18nLabel: 'WebRTC_Enable_Channel', |
||||
enableQuery: { |
||||
_id: 'bigbluebutton_Enabled', |
||||
value: true, |
||||
}, |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Enable_Channels', false, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Jitsi_Enable_Channels', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Chrome_Extension', 'nocfbnnmjnndkbipkabodnheejiegccf', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Jitsi_Chrome_Extension', |
||||
public: true, |
||||
this.section('Jitsi', function() { |
||||
this.add('Jitsi_Enabled', false, { |
||||
type: 'boolean', |
||||
i18nLabel: 'Enabled', |
||||
alert: 'This Feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Domain', 'meet.jit.si', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Domain', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_URL_Room_Prefix', 'RocketChat', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'URL_room_prefix', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_SSL', true, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'SSL', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Open_New_Window', false, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Always_open_in_new_window', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Enable_Channels', false, { |
||||
type: 'boolean', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Jitsi_Enable_Channels', |
||||
public: true, |
||||
}); |
||||
|
||||
this.add('Jitsi_Chrome_Extension', 'nocfbnnmjnndkbipkabodnheejiegccf', { |
||||
type: 'string', |
||||
enableQuery: { |
||||
_id: 'Jitsi_Enabled', |
||||
value: true, |
||||
}, |
||||
i18nLabel: 'Jitsi_Chrome_Extension', |
||||
public: true, |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
Loading…
Reference in new issue