mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-27 12:33:21 +00:00
Compare commits
1 Commits
4357
...
jibri-queu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7cb719be0 |
@@ -759,13 +759,7 @@ export default {
|
||||
}
|
||||
|
||||
if (isPrejoinPageEnabled(APP.store.getState())) {
|
||||
_connectionPromise = connect(roomName).then(c => {
|
||||
// we want to initialize it early, in case of errors to be able
|
||||
// to gather logs
|
||||
APP.connection = c;
|
||||
|
||||
return c;
|
||||
});
|
||||
_connectionPromise = connect(roomName);
|
||||
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
const tracks = await tryCreateLocalTracks;
|
||||
@@ -1212,6 +1206,10 @@ export default {
|
||||
|
||||
// end used by torture
|
||||
|
||||
getLogs() {
|
||||
return room.getLogs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Download logs, a function that can be called from console while
|
||||
* debugging.
|
||||
@@ -1220,7 +1218,7 @@ export default {
|
||||
saveLogs(filename = 'meetlog.json') {
|
||||
// this can be called from console and will not have reference to this
|
||||
// that's why we reference the global var
|
||||
const logs = APP.connection.getLogs();
|
||||
const logs = APP.conference.getLogs();
|
||||
const data = encodeURIComponent(JSON.stringify(logs, null, ' '));
|
||||
|
||||
const elem = document.createElement('a');
|
||||
|
||||
@@ -82,7 +82,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
// Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption
|
||||
// that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified.
|
||||
@@ -94,7 +94,11 @@ function connect(id, password, roomName) {
|
||||
// in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability.
|
||||
connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl;
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig);
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
null,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
connectionConfig);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
@@ -207,9 +211,10 @@ export function openConnection({ id, password, retry, roomName }) {
|
||||
|
||||
return connect(id, password, roomName).catch(err => {
|
||||
if (retry) {
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& (!jwt || issuer === 'anonymous')) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
}
|
||||
}
|
||||
|
||||
74
css/_notifications.scss
Normal file
74
css/_notifications.scss
Normal file
@@ -0,0 +1,74 @@
|
||||
@include keyframes(exiting) {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate(-200px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.notificationsContainer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
flex-direction: column;
|
||||
width: 400px;
|
||||
left: 80px;
|
||||
|
||||
.topContainer {
|
||||
padding-bottom: 16px;
|
||||
// transition: height 0.4s ease-in-out;
|
||||
|
||||
.notification {
|
||||
width: 400px;
|
||||
z-index: 5;
|
||||
|
||||
&:nth-child(n+2) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.exiting {
|
||||
animation-name: exiting;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottomContainer {
|
||||
margin-bottom: 64px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.notification {
|
||||
width: 400px;
|
||||
bottom: 0px;
|
||||
|
||||
&:nth-child(1) {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
&:nth-child(n+2) {
|
||||
transition: transform 0.4s ease-in-out;
|
||||
z-index: 4;
|
||||
position: absolute;
|
||||
transform: translateY(100%) translateY(16px);
|
||||
}
|
||||
|
||||
|
||||
&:nth-child(n+4) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.exiting {
|
||||
animation-name: exiting;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
&.exiting+.notification {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,3 +115,19 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.jibri-queue-info {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
.footer {
|
||||
background: #a4b8a4D1;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
color: #5e6d7a;
|
||||
padding: 5px;
|
||||
margin-right: 30px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -102,5 +102,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'premeeting-screens';
|
||||
@import 'e2ee';
|
||||
@import 'responsive';
|
||||
@import 'notifications';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -45,10 +45,8 @@ server {
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_types text/plain text/css application/javascript application/json;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
location = /config.js {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
@@ -63,11 +61,6 @@ server {
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
|
||||
# cache all versioned files
|
||||
if ($arg_v) {
|
||||
expires 1y;
|
||||
}
|
||||
}
|
||||
|
||||
# BOSH
|
||||
|
||||
@@ -14,12 +14,6 @@ server {
|
||||
ssi on;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
|
||||
@@ -28,12 +28,6 @@ server {
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
|
||||
gzip_vary on;
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_min_length 512;
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
"bandwidth": "Estimated bandwidth:",
|
||||
"bitrate": "Bitrate:",
|
||||
"bridgeCount": "Server count: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Connected to:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
"framerate": "Frame rate:",
|
||||
@@ -217,7 +216,9 @@
|
||||
"kickParticipantDialog": "Are you sure you want to kick this participant?",
|
||||
"kickParticipantTitle": "Kick this participant?",
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"leaveJibriQueue": "Exit queue",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"leaveJibriQueueWarning": "Are you sure you would like to exit the queue?",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
@@ -909,5 +910,22 @@
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
"toggleLabel": "Enable lobby"
|
||||
},
|
||||
"jibriQueue": {
|
||||
"recording": {
|
||||
"title": "You have joined a recording queue!",
|
||||
"time": "Estimated time for starting the recording: {{time}}",
|
||||
"footer": "For unlimited recordings you should subscribe to 8x8 Meetings",
|
||||
"left": "You have left the recording queue!"
|
||||
},
|
||||
"livestreaming": {
|
||||
"title": "You have joined a live streaming queue!",
|
||||
"time": "Estimated time for starting the live streaming: {{time}}",
|
||||
"footer": "For unlimited live streaming you should subscribe to 8x8 Meetings",
|
||||
"left": "You have left the live streaming queue!"
|
||||
},
|
||||
"position": "{{count}} more person is waiting in front of you.",
|
||||
"position_plural": "{{count}} more people are waiting in front of you.",
|
||||
"exit": "Exit queue"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
const jibriQueueJID = state['features/base/config'].jibriQueueJID;
|
||||
let recordingConfig;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
@@ -251,7 +252,8 @@ function initCommands() {
|
||||
'token': dropboxToken
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
jibriQueueJID
|
||||
};
|
||||
} else {
|
||||
recordingConfig = {
|
||||
@@ -260,12 +262,14 @@ function initCommands() {
|
||||
'file_recording_metadata': {
|
||||
'share': shouldShare
|
||||
}
|
||||
})
|
||||
}),
|
||||
jibriQueueJID
|
||||
};
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID,
|
||||
jibriQueueJID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey
|
||||
};
|
||||
@@ -275,7 +279,9 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
conference.startRecording(recordingConfig);
|
||||
conference.startRecording(recordingConfig).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -302,8 +308,10 @@ function initCommands() {
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (activeSession && activeSession.id) {
|
||||
conference.stopRecording(activeSession.id);
|
||||
if (activeSession && (activeSession.id || activeSession.queueID)) {
|
||||
conference.stopRecording(activeSession.id, activeSession.queueID).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
} else {
|
||||
logger.error('No recording or streaming session found');
|
||||
}
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -982,9 +982,9 @@
|
||||
}
|
||||
},
|
||||
"@atlaskit/portal": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/portal/-/portal-4.0.1.tgz",
|
||||
"integrity": "sha512-dYe/YozUkFZ0NitZ2dfnHE/d8i1Tq+pcdlwXd3+PyhzvPbnPoseZpQ/jC+KAbxZ4wCzLwSF+SXapdAJSbwkLfg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/portal/-/portal-4.0.0.tgz",
|
||||
"integrity": "sha512-FLvq90T2zt7bUOUkOb90xbB1JGOI77456euDwrz1d9NYVoe+kSQr4Xau7kQLUgmpzmH4Sd4BHOvp50JHJ0qezw==",
|
||||
"requires": {
|
||||
"@atlaskit/theme": "^10.0.0",
|
||||
"exenv": "^1.2.2",
|
||||
@@ -993,9 +993,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/theme": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-10.0.1.tgz",
|
||||
"integrity": "sha512-XUor9lYlX0yTRSxd/rvaL8i2gdm0PDbOV+KhuezpuGBaS0opzseRrCnEc+OMGmpWRYHjCRyEugp6FwSSquFb8w==",
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-10.0.2.tgz",
|
||||
"integrity": "sha512-TfzWnISbO9R9BkBpu2PD1bvGku2LqFkzhcBiJUAjWcL2E5nIt9NZIlnnCZ4A94510Lnd4B/orcghh5iid5R/LA==",
|
||||
"requires": {
|
||||
"exenv": "^1.2.2",
|
||||
"prop-types": "^15.5.10",
|
||||
@@ -17945,8 +17945,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#15dcc57424cc937290e1963b8eb402c1fcf48ccb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#15dcc57424cc937290e1963b8eb402c1fcf48ccb",
|
||||
"version": "github:jitsi/lib-jitsi-meet#f74cd0abe9c696a9c3ca7dbb9ca170e6e84d6756",
|
||||
"from": "github:jitsi/lib-jitsi-meet#f74cd0abe9c696a9c3ca7dbb9ca170e6e84d6756",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.0",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@atlaskit/tabs": "8.0.11",
|
||||
"@atlaskit/theme": "7.0.2",
|
||||
"@atlaskit/toggle": "5.0.14",
|
||||
"@atlaskit/portal": "4.0.0",
|
||||
"@atlaskit/tooltip": "12.1.13",
|
||||
"@jitsi/js-utils": "1.0.1",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
@@ -56,7 +57,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#15dcc57424cc937290e1963b8eb402c1fcf48ccb",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f74cd0abe9c696a9c3ca7dbb9ca170e6e84d6756",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
|
||||
@@ -43,8 +43,8 @@ export const SET_CONFIG = 'SET_CONFIG';
|
||||
* and the passed object.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_CONFIG,
|
||||
* type: _UPDATE_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
|
||||
export const _UPDATE_CONFIG = '_UPDATE_CONFIG';
|
||||
|
||||
@@ -6,24 +6,10 @@ import type { Dispatch } from 'redux';
|
||||
import { addKnownDomains } from '../known-domains';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG, UPDATE_CONFIG } from './actionTypes';
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
import { setConfigFromURLParams } from './functions';
|
||||
|
||||
|
||||
/**
|
||||
* Updates the config with new options.
|
||||
*
|
||||
* @param {Object} config - The new options (to add).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateConfig(config: Object) {
|
||||
return {
|
||||
type: UPDATE_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the configuration (commonly known in Jitsi Meet as config.js)
|
||||
* for a specific locationURL will be loaded now.
|
||||
|
||||
@@ -8,8 +8,7 @@ import { addKnownDomains } from '../known-domains';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import { SET_CONFIG } from './actionTypes';
|
||||
import { updateConfig } from './actions';
|
||||
import { _UPDATE_CONFIG, SET_CONFIG } from './actionTypes';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
|
||||
/**
|
||||
@@ -115,7 +114,10 @@ function _setConfig({ dispatch, getState }, next, action) {
|
||||
config.resolution = resolutionFlag;
|
||||
}
|
||||
|
||||
dispatch(updateConfig(config));
|
||||
dispatch({
|
||||
type: _UPDATE_CONFIG,
|
||||
config
|
||||
});
|
||||
|
||||
// FIXME On Web we rely on the global 'config' variable which gets altered
|
||||
// multiple times, before it makes it to the reducer. At some point it may
|
||||
|
||||
@@ -4,7 +4,7 @@ import _ from 'lodash';
|
||||
|
||||
import { equals, ReducerRegistry, set } from '../redux';
|
||||
|
||||
import { UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { _UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import { _cleanupConfig } from './functions';
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ const INITIAL_RN_STATE = {
|
||||
|
||||
ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_CONFIG:
|
||||
case _UPDATE_CONFIG:
|
||||
return _updateConfig(state, action);
|
||||
|
||||
case CONFIG_WILL_LOAD:
|
||||
|
||||
@@ -80,8 +80,12 @@ export function connect(id: ?string, password: ?string) {
|
||||
const state = getState();
|
||||
const options = _constructOptions(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
||||
const { issuer, jwt } = state['features/base/jwt'];
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
options.appId,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
options);
|
||||
|
||||
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/jwt');
|
||||
@@ -13,7 +13,6 @@ import { MiddlewareRegistry } from '../redux';
|
||||
import { SET_JWT } from './actionTypes';
|
||||
import { setJWT } from './actions';
|
||||
import { parseJWTFromURLParams } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -134,13 +133,7 @@ function _setJWT(store, next, action) {
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
let jwtPayload;
|
||||
|
||||
try {
|
||||
jwtPayload = jwtDecode(jwt);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
const jwtPayload = jwtDecode(jwt);
|
||||
|
||||
if (jwtPayload) {
|
||||
const { context, iss } = jwtPayload;
|
||||
|
||||
@@ -340,7 +340,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
bandwidth,
|
||||
bitrate,
|
||||
bridgeCount,
|
||||
codec,
|
||||
e2eRtt,
|
||||
framerate,
|
||||
maxEnabledResolution,
|
||||
@@ -356,7 +355,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
bandwidth = { bandwidth }
|
||||
bitrate = { bitrate }
|
||||
bridgeCount = { bridgeCount }
|
||||
codec = { codec }
|
||||
connectionSummary = { this._getConnectionStatusTip() }
|
||||
e2eRtt = { e2eRtt }
|
||||
framerate = { framerate }
|
||||
|
||||
@@ -122,7 +122,6 @@ const statsEmitter = {
|
||||
_onStatsUpdated(localUserId: string, stats: Object) {
|
||||
const allUserFramerates = stats.framerate || {};
|
||||
const allUserResolutions = stats.resolution || {};
|
||||
const allUserCodecs = stats.codec || {};
|
||||
|
||||
// FIXME resolution and framerate are maps keyed off of user ids with
|
||||
// stat values. Receivers of stats expect resolution and framerate to
|
||||
@@ -130,8 +129,7 @@ const statsEmitter = {
|
||||
// stats objects.
|
||||
const modifiedLocalStats = Object.assign({}, stats, {
|
||||
framerate: allUserFramerates[localUserId],
|
||||
resolution: allUserResolutions[localUserId],
|
||||
codec: allUserCodecs[localUserId]
|
||||
resolution: allUserResolutions[localUserId]
|
||||
});
|
||||
|
||||
this._emitStatsUpdate(localUserId, modifiedLocalStats);
|
||||
@@ -140,9 +138,8 @@ const statsEmitter = {
|
||||
// and update remote user stats as needed.
|
||||
const framerateUserIds = Object.keys(allUserFramerates);
|
||||
const resolutionUserIds = Object.keys(allUserResolutions);
|
||||
const codecUserIds = Object.keys(allUserCodecs);
|
||||
|
||||
_.union(framerateUserIds, resolutionUserIds, codecUserIds)
|
||||
_.union(framerateUserIds, resolutionUserIds)
|
||||
.filter(id => id !== localUserId)
|
||||
.forEach(id => {
|
||||
const remoteUserStats = {};
|
||||
@@ -159,12 +156,6 @@ const statsEmitter = {
|
||||
remoteUserStats.resolution = resolution;
|
||||
}
|
||||
|
||||
const codec = allUserCodecs[id];
|
||||
|
||||
if (codec) {
|
||||
remoteUserStats.codec = codec;
|
||||
}
|
||||
|
||||
this._emitStatsUpdate(id, remoteUserStats);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,11 +34,6 @@ type Props = {
|
||||
*/
|
||||
bridgeCount: number,
|
||||
|
||||
/**
|
||||
* Audio/video codecs in use for the connection.
|
||||
*/
|
||||
codec: Object,
|
||||
|
||||
/**
|
||||
* A message describing the connection quality.
|
||||
*/
|
||||
@@ -224,45 +219,6 @@ class ConnectionStatsTable extends Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a a table row as a ReactElement for displaying codec, if present.
|
||||
* This will typically be something like "Codecs (A/V): opus, vp8".
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderCodecs() {
|
||||
const { codec, t } = this.props;
|
||||
|
||||
if (!codec) {
|
||||
return;
|
||||
}
|
||||
|
||||
let codecString;
|
||||
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
|
||||
if (!codecString) {
|
||||
codecString = 'N/A';
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<span>{ t('connectionindicator.codecs') }</span>
|
||||
</td>
|
||||
<td>{ codecString }</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a table row as a ReactElement for displaying a summary message
|
||||
* about the current connection status.
|
||||
@@ -496,7 +452,6 @@ class ConnectionStatsTable extends Component<Props> {
|
||||
{ isRemoteVideo ? this._renderRegion() : null }
|
||||
{ this._renderResolution() }
|
||||
{ this._renderFrameRate() }
|
||||
{ this._renderCodecs() }
|
||||
{ isRemoteVideo ? null : this._renderBridgeCount() }
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import { FlagGroup } from '@atlaskit/flag';
|
||||
import Portal from '@atlaskit/portal';
|
||||
import React from 'react';
|
||||
import { Transition, TransitionGroup } from 'react-transition-group';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractNotificationsContainer, {
|
||||
@@ -27,6 +28,16 @@ type Props = AbstractProps & {
|
||||
* @extends {Component}
|
||||
*/
|
||||
class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
||||
/**
|
||||
* Creates new NotificationContainer instance.
|
||||
*
|
||||
* @param {Props} props - The props of the react component.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._renderNotification = this._renderNotification.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
@@ -40,39 +51,92 @@ class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<FlagGroup onDismissed = { this._onDismissed }>
|
||||
{ this._renderFlags() }
|
||||
</FlagGroup>
|
||||
<Portal zIndex = { 600 }>
|
||||
<div className = 'notificationsContainer'>
|
||||
{ this._renderTopNotificationsContainer() }
|
||||
{ this._renderBottomNotificationsContainer() }
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
_onDismissed: number => void;
|
||||
|
||||
/**
|
||||
* Renders notifications to display as ReactElements. An empty array will
|
||||
* be returned if notifications are disabled.
|
||||
* Renders the bottom notification container.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement[]}
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderFlags() {
|
||||
_renderBottomNotificationsContainer() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
return _notifications.map(notification => {
|
||||
const { props, uid } = notification;
|
||||
return (
|
||||
<TransitionGroup className = 'bottomContainer'>
|
||||
{
|
||||
_notifications.filter(n => n.props.position !== 'top').map((notification, index) => {
|
||||
const { props, uid } = notification;
|
||||
|
||||
// The id attribute is necessary as {@code FlagGroup} looks for
|
||||
// either id or key to set a key on notifications, but accessing
|
||||
// props.key will cause React to print an error.
|
||||
return (
|
||||
<Notification
|
||||
{ ...props }
|
||||
id = { uid }
|
||||
key = { uid }
|
||||
uid = { uid } />
|
||||
return this._renderNotification({
|
||||
...props,
|
||||
isDismissAllowed: index > 0 ? false : props.isDismissAllowed
|
||||
}, uid);
|
||||
})
|
||||
}
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
|
||||
);
|
||||
});
|
||||
_renderNotification: (string, number) => Function;
|
||||
|
||||
/**
|
||||
* Renders a notification.
|
||||
*
|
||||
* @param {Object} props - The props for the Notification component.
|
||||
* @param {string} uid - A unique ID for the notification.
|
||||
* @returns {Function} - Returns a transition function for the Transition component.
|
||||
*/
|
||||
_renderNotification(props, uid) {
|
||||
return (
|
||||
<Transition
|
||||
key = { uid }
|
||||
timeout = { 400 }>
|
||||
{
|
||||
transitionState => (
|
||||
<div className = { `notification ${transitionState}` }>
|
||||
<Notification
|
||||
{ ...props }
|
||||
id = { uid }
|
||||
key = { uid }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { uid } />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the top notifications container.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderTopNotificationsContainer() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
return (
|
||||
<TransitionGroup className = 'topContainer'>
|
||||
{
|
||||
_notifications.filter(n => n.props.position === 'top').map(notification => {
|
||||
const { props, uid } = notification;
|
||||
|
||||
return this._renderNotification(props, uid);
|
||||
})
|
||||
}
|
||||
</TransitionGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,13 +201,11 @@ export function initPrejoin(tracks: Object[], errors: Object) {
|
||||
/**
|
||||
* Action used to start the conference.
|
||||
*
|
||||
* @param {Object} options - The config options that override the default ones (if any).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinConference(options?: Object) {
|
||||
export function joinConference() {
|
||||
return {
|
||||
type: PREJOIN_START_CONFERENCE,
|
||||
options
|
||||
type: PREJOIN_START_CONFERENCE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -224,10 +222,7 @@ export function joinConferenceWithoutAudio() {
|
||||
if (audioTrack) {
|
||||
await dispatch(replaceLocalTrack(audioTrack, null));
|
||||
}
|
||||
|
||||
dispatch(joinConference({
|
||||
startSilent: true
|
||||
}));
|
||||
dispatch(joinConference());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { updateConfig } from '../base/config';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { updateSettings } from '../base/settings';
|
||||
@@ -25,9 +24,6 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
const state = getState();
|
||||
const { userSelectedSkipPrejoin } = state['features/prejoin'];
|
||||
const localVideoTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const { options } = action;
|
||||
|
||||
options && store.dispatch(updateConfig(options));
|
||||
|
||||
userSelectedSkipPrejoin && dispatch(updateSettings({
|
||||
userSelectedSkipPrejoin
|
||||
|
||||
@@ -46,3 +46,17 @@ export const SET_PENDING_RECORDING_NOTIFICATION_UID
|
||||
* }
|
||||
*/
|
||||
export const SET_STREAM_KEY = 'SET_STREAM_KEY';
|
||||
|
||||
/**
|
||||
* The type of Redux action which sets the waiting in queue recording notification UID to
|
||||
* use it for when hiding the notification is necessary, or unsets it when
|
||||
* undefined (or no param) is passed.
|
||||
*
|
||||
* {
|
||||
* type: SET_WAITING_IN_RECORDING_NOTIFICATION_UID,
|
||||
* streamType: string,
|
||||
* uid: ?number
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_WAITING_IN_RECORDING_NOTIFICATION_UID = 'SET_WAITING_IN_RECORDING_NOTIFICATION_UID';
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT,
|
||||
NOTIFICATION_TYPE,
|
||||
hideNotification,
|
||||
showErrorNotification,
|
||||
showNotification
|
||||
@@ -12,8 +16,10 @@ import {
|
||||
CLEAR_RECORDING_SESSIONS,
|
||||
RECORDING_SESSION_UPDATED,
|
||||
SET_PENDING_RECORDING_NOTIFICATION_UID,
|
||||
SET_STREAM_KEY
|
||||
SET_STREAM_KEY,
|
||||
SET_WAITING_IN_RECORDING_NOTIFICATION_UID
|
||||
} from './actionTypes';
|
||||
import { QueueInfo, StopLiveStreamDialog, StopRecordingDialog } from './components';
|
||||
|
||||
/**
|
||||
* Clears the data of every recording sessions.
|
||||
@@ -50,6 +56,25 @@ export function hidePendingRecordingNotification(streamType: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the waiting in queue recording notification should be removed from the screen.
|
||||
*
|
||||
* @param {string} streamType - The type of the stream ({@code 'file'} or
|
||||
* {@code 'stream'}).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hideWaitingInQueueRecordingNotification(streamType: string) {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
const { waitingInQueueNotificationUids } = getState()['features/recording'];
|
||||
const waitingInQueueNotificationUid = waitingInQueueNotificationUids[streamType];
|
||||
|
||||
if (waitingInQueueNotificationUid) {
|
||||
dispatch(hideNotification(waitingInQueueNotificationUid));
|
||||
dispatch(_setWaitingInQueueRecordingNotificationUid(undefined, streamType));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stream key last used by the user for later reuse.
|
||||
*
|
||||
@@ -97,6 +122,22 @@ export function showPendingRecordingNotification(streamType: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the jibri queue has been left and notification should be shown on the
|
||||
* screen.
|
||||
*
|
||||
* @param {string} streamType - The type of the stream ({@code file} or
|
||||
* {@code stream}).
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
export function showQueueLeftRecordingNotification(streamType: string) {
|
||||
const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
return showNotification({
|
||||
titleKey: `jibriQueue.${isLiveStreaming ? 'livestreaming' : 'recording'}.left`
|
||||
}, NOTIFICATION_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the recording error notification should be shown.
|
||||
*
|
||||
@@ -175,6 +216,15 @@ export function updateRecordingSessionData(session: Object) {
|
||||
= status === JitsiRecordingConstants.status.ON
|
||||
? Date.now() / 1000
|
||||
: undefined;
|
||||
const queueID = session.getQueueID();
|
||||
let queueEstimatedTimeOfStart, queuePosition;
|
||||
|
||||
if (status === JitsiRecordingConstants.status.WAITING_IN_QUEUE) {
|
||||
const { position, estimatedTimeLeft } = session.getQueueMetrics();
|
||||
|
||||
queuePosition = position;
|
||||
queueEstimatedTimeOfStart = (new Date()).getTime() + (estimatedTimeLeft * 1000);
|
||||
}
|
||||
|
||||
return {
|
||||
type: RECORDING_SESSION_UPDATED,
|
||||
@@ -186,7 +236,10 @@ export function updateRecordingSessionData(session: Object) {
|
||||
mode: session.getMode(),
|
||||
status,
|
||||
terminator: session.getTerminator(),
|
||||
timestamp
|
||||
timestamp,
|
||||
queueID,
|
||||
queuePosition,
|
||||
queueEstimatedTimeOfStart
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -212,3 +265,57 @@ function _setPendingRecordingNotificationUid(uid: ?number, streamType: string) {
|
||||
uid
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets UID of the the pending streaming notification to use it when hiding
|
||||
* the notification is necessary, or unsets it when undefined (or no param) is
|
||||
* passed.
|
||||
*
|
||||
* @param {?number} uid - The UID of the notification.
|
||||
* @param {string} streamType - The type of the stream ({@code file} or {@code stream}).
|
||||
* @returns {{
|
||||
* type: SET_PENDING_RECORDING_NOTIFICATION_UID,
|
||||
* streamType: string,
|
||||
* uid: number
|
||||
* }}
|
||||
*/
|
||||
function _setWaitingInQueueRecordingNotificationUid(uid: ?number, streamType: string) {
|
||||
return {
|
||||
type: SET_WAITING_IN_RECORDING_NOTIFICATION_UID,
|
||||
streamType,
|
||||
uid
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the recording queue notification should be shown on the screen.
|
||||
*
|
||||
* @param {string} streamType - The type of the stream ({@code file} or
|
||||
* {@code stream}).
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showWaitingInQueueRecordingNotification(streamType: string) {
|
||||
return (dispatch: Function) => {
|
||||
const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
const showNotificationAction = showNotification({
|
||||
appearance: NOTIFICATION_TYPE.INFO,
|
||||
customActionNameKey: 'jibriQueue.exit',
|
||||
customActionHandler: () => {
|
||||
if (isLiveStreaming) {
|
||||
dispatch(openDialog(StopLiveStreamDialog));
|
||||
} else {
|
||||
dispatch(openDialog(StopRecordingDialog));
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
position: 'top',
|
||||
titleKey: `jibriQueue.${isLiveStreaming ? 'livestreaming' : 'recording'}.title`,
|
||||
description: <QueueInfo />
|
||||
});
|
||||
|
||||
dispatch(showNotificationAction);
|
||||
dispatch(_setWaitingInQueueRecordingNotificationUid(
|
||||
showNotificationAction.uid, streamType));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -217,6 +217,8 @@ export default class AbstractStartLiveStreamDialog<P: Props>
|
||||
broadcastId: selectedBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: key
|
||||
}).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -65,7 +65,9 @@ export default class AbstractStopLiveStreamDialog extends Component<Props> {
|
||||
const { _session } = this.props;
|
||||
|
||||
if (_session) {
|
||||
this.props._conference.stopRecording(_session.id);
|
||||
this.props._conference.stopRecording(_session.id, _session.queueID).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import AbstractStopLiveStreamDialog, {
|
||||
_mapStateToProps
|
||||
@@ -24,13 +25,17 @@ class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _session = {}, t } = this.props;
|
||||
|
||||
const isInQueue = _session.status === JitsiRecordingConstants.status.WAITING_IN_QUEUE;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okKey = 'dialog.stopLiveStreaming'
|
||||
okKey = { isInQueue ? 'dialog.leaveJibriQueue' : 'dialog.stopLiveStreaming' }
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.liveStreaming'
|
||||
width = 'small'>
|
||||
{ this.props.t('dialog.stopStreamingWarning') }
|
||||
{ t(isInQueue ? 'dialog.leaveJibriQueueWarning' : 'dialog.stopStreamingWarning') }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -280,6 +280,8 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
|
||||
_conference.startRecording({
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData
|
||||
}).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
|
||||
if (_autoCaptionOnRecord) {
|
||||
|
||||
@@ -65,7 +65,9 @@ export default class AbstractStopRecordingDialog<P: Props>
|
||||
const { _fileRecordingSession } = this.props;
|
||||
|
||||
if (_fileRecordingSession) {
|
||||
this.props._conference.stopRecording(_fileRecordingSession.id);
|
||||
this.props._conference.stopRecording(_fileRecordingSession.id, _fileRecordingSession.queueID).catch(() => {
|
||||
// prevent unhandled promise rejection.
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import AbstractStopRecordingDialog, {
|
||||
type Props,
|
||||
@@ -24,15 +25,17 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { _fileRecordingSession = {}, t } = this.props;
|
||||
|
||||
const isInQueue = _fileRecordingSession.status === JitsiRecordingConstants.status.WAITING_IN_QUEUE;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okKey = 'dialog.confirm'
|
||||
okKey = { isInQueue ? 'dialog.leaveJibriQueue' : 'dialog.confirm' }
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.recording'
|
||||
width = 'small'>
|
||||
{ t('dialog.stopRecordingWarning') }
|
||||
{ t(isInQueue ? 'dialog.leaveJibriQueueWarning' : 'dialog.stopRecordingWarning') }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
230
react/features/recording/components/web/QueueInfo.js
Normal file
230
react/features/recording/components/web/QueueInfo.js
Normal file
@@ -0,0 +1,230 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { getLocalizedDurationFormatter, translate } from '../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current position of the participant in the queue.
|
||||
*/
|
||||
_position: ?string,
|
||||
|
||||
/**
|
||||
* The recording mode.
|
||||
*/
|
||||
_mode: string,
|
||||
|
||||
/**
|
||||
* The ID of the queue.
|
||||
*/
|
||||
_queueID: string,
|
||||
|
||||
/**
|
||||
* The time when the recording is expected to start.
|
||||
*/
|
||||
_estimatedTimeOfStart: number,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link QueueInfo}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The current value of the timer for estimated time left.
|
||||
*/
|
||||
timerValue: ?string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays the current state of the Jibri Queue.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class QueueInfo extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Handle for setInterval timer.
|
||||
*/
|
||||
_interval: IntervalID;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code QueueInfo} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
timerValue: undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the timer when component will be unmounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this._stopTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the timer when component will be mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
if (typeof this.props._estimatedTimeOfStart !== 'undefined') {
|
||||
this._startTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React Component's componentDidUpdate.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props._estimatedTimeOfStart !== prevProps._estimatedTimeOfStart) {
|
||||
this._stopTimer(false);
|
||||
this._startTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current state values that will be used to render the timer.
|
||||
*
|
||||
* @param {number} refValueUTC - The initial UTC timestamp value.
|
||||
* @param {number} currentValueUTC - The current UTC timestamp value.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_setStateFromUTC(refValueUTC, currentValueUTC) {
|
||||
if (!refValueUTC || !currentValueUTC) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timerMsValue = currentValueUTC > refValueUTC ? 0 : refValueUTC - currentValueUTC;
|
||||
const localizedTime = getLocalizedDurationFormatter(timerMsValue);
|
||||
|
||||
this.setState({
|
||||
timerValue: localizedTime
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the timer.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_startTimer() {
|
||||
const { _estimatedTimeOfStart } = this.props;
|
||||
|
||||
if (!this._interval && typeof _estimatedTimeOfStart !== 'undefined') {
|
||||
this._setStateFromUTC(_estimatedTimeOfStart, (new Date()).getTime());
|
||||
this._interval = setInterval(() => {
|
||||
this._setStateFromUTC(_estimatedTimeOfStart, (new Date()).getTime());
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the timer.
|
||||
*
|
||||
* @param {boolean} [clearState] - If true, the timer value in the state will be cleared.
|
||||
* @returns {void}
|
||||
*/
|
||||
_stopTimer(clearState = true) {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
delete this._interval;
|
||||
}
|
||||
|
||||
if (clearState) {
|
||||
this.setState({
|
||||
timerValue: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React {@code Component}'s render.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _estimatedTimeOfStart, _mode, _position = 0, t } = this.props;
|
||||
const { STREAM } = JitsiRecordingConstants.mode;
|
||||
const timeTextKey = `jibriQueue.${_mode === STREAM ? 'livestreaming' : 'recording'}.time`;
|
||||
const { timerValue } = this.state;
|
||||
const footerText = t(`jibriQueue.${_mode === STREAM ? 'livestreaming' : 'recording'}.footer`);
|
||||
const showFooter = typeof footerText === 'string' && footerText.length > 0;
|
||||
|
||||
|
||||
return (
|
||||
<div className = 'jibri-queue-info'>
|
||||
<span className = 'position'>
|
||||
{ t('jibriQueue.position', { count: _position }) }
|
||||
</span>
|
||||
{
|
||||
typeof _estimatedTimeOfStart === 'undefined' || timerValue === 'undefined'
|
||||
? null : <span className = 'time'>
|
||||
{ t(timeTextKey, { time: timerValue }) }
|
||||
</span>
|
||||
}
|
||||
{
|
||||
showFooter ? <div className = 'footer'>{ footerText }</div> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code AbstractRecordingLabel}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _estimatedTimeOfStart: number,
|
||||
* _mode: string,
|
||||
* _position: string,
|
||||
* _queueID: string,
|
||||
* t: Function
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object) {
|
||||
const session = getActiveSession(state);
|
||||
|
||||
if (!session) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { id, mode, queueEstimatedTimeOfStart, queueID, queuePosition } = session;
|
||||
|
||||
return {
|
||||
_sessionID: id,
|
||||
_mode: mode,
|
||||
_queueID: queueID,
|
||||
_position: queuePosition,
|
||||
_estimatedTimeOfStart: queueEstimatedTimeOfStart
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(QueueInfo));
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
export { default as RecordingLabel } from './RecordingLabel';
|
||||
export { default as RecordingLimitNotificationDescription } from './RecordingLimitNotificationDescription';
|
||||
export { default as QueueInfo } from './QueueInfo';
|
||||
|
||||
@@ -9,16 +9,17 @@ import { RECORDING_STATUS_PRIORITIES } from './constants';
|
||||
* passed in mode.
|
||||
*
|
||||
* @param {Object} state - The redux state to search in.
|
||||
* @param {string} mode - Find an active recording session of the given mode.
|
||||
* @param {string|undefined} mode - Find an active recording session of the given mode.
|
||||
* @returns {Object|undefined}
|
||||
*/
|
||||
export function getActiveSession(state: Object, mode: string) {
|
||||
export function getActiveSession(state: Object, mode: ?string) {
|
||||
const { sessionDatas } = state['features/recording'];
|
||||
const { status: statusConstants } = JitsiRecordingConstants;
|
||||
|
||||
return sessionDatas.find(sessionData => sessionData.mode === mode
|
||||
return sessionDatas.find(sessionData => (typeof mode === 'undefined' || sessionData.mode === mode)
|
||||
&& (sessionData.status === statusConstants.ON
|
||||
|| sessionData.status === statusConstants.PENDING));
|
||||
|| sessionData.status === statusConstants.PENDING
|
||||
|| sessionData.status === statusConstants.WAITING_IN_QUEUE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,6 +38,8 @@ export function getRecordingDurationEstimation(size: ?number) {
|
||||
* Searches in the passed in redux state for a recording session that matches
|
||||
* the passed in recording session ID.
|
||||
*
|
||||
* NOTE: The sessoins in WAITING_IN_QUEUE status don't have ID yet.
|
||||
*
|
||||
* @param {Object} state - The redux state to search in.
|
||||
* @param {string} id - The ID of the recording session to find.
|
||||
* @returns {Object|undefined}
|
||||
@@ -51,6 +54,8 @@ export function getSessionById(state: Object, id: string) {
|
||||
* there is a session with the status OFF and one with PENDING, then the PENDING
|
||||
* one will be shown, because that is likely more important for the user to see.
|
||||
*
|
||||
* NOTE: For all "queue" statuses the function returns undefined because we don't want to show label.
|
||||
*
|
||||
* @param {Object} state - The redux state to search in.
|
||||
* @param {string} mode - The recording mode to get status for.
|
||||
* @returns {string|undefined}
|
||||
|
||||
@@ -24,11 +24,14 @@ import { RECORDING_SESSION_UPDATED } from './actionTypes';
|
||||
import {
|
||||
clearRecordingSessions,
|
||||
hidePendingRecordingNotification,
|
||||
hideWaitingInQueueRecordingNotification,
|
||||
showPendingRecordingNotification,
|
||||
showQueueLeftRecordingNotification,
|
||||
showRecordingError,
|
||||
showRecordingLimitNotification,
|
||||
showStartedRecordingNotification,
|
||||
showStoppedRecordingNotification,
|
||||
showWaitingInQueueRecordingNotification,
|
||||
updateRecordingSessionData
|
||||
} from './actions';
|
||||
import {
|
||||
@@ -110,15 +113,16 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.RECORDER_STATE_CHANGED,
|
||||
recorderSession => {
|
||||
|
||||
if (recorderSession) {
|
||||
recorderSession.getID()
|
||||
&& dispatch(
|
||||
if (recorderSession.getID() || recorderSession.getQueueID()) {
|
||||
dispatch(
|
||||
updateRecordingSessionData(recorderSession));
|
||||
}
|
||||
|
||||
recorderSession.getError()
|
||||
&& _showRecordingErrorNotification(
|
||||
if (recorderSession.getError()) {
|
||||
_showRecordingErrorNotification(
|
||||
recorderSession, dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -142,75 +146,91 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
const updatedSessionData
|
||||
= getSessionById(getState(), action.sessionData.id);
|
||||
const { initiator, mode, terminator } = updatedSessionData;
|
||||
const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
|
||||
const updatedSessionData = getSessionById(getState(), action.sessionData.id);
|
||||
const { initiator, mode, status: newStatus, terminator } = updatedSessionData;
|
||||
const { PENDING, OFF, ON, WAITING_IN_QUEUE, QUEUE_LEFT } = JitsiRecordingConstants.status;
|
||||
|
||||
if (updatedSessionData.status === PENDING
|
||||
&& (!oldSessionData || oldSessionData.status !== PENDING)) {
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
} else if (updatedSessionData.status !== PENDING) {
|
||||
if (oldSessionData && oldSessionData.status === newStatus) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (newStatus !== WAITING_IN_QUEUE) {
|
||||
dispatch(hideWaitingInQueueRecordingNotification(mode));
|
||||
}
|
||||
|
||||
if (newStatus !== PENDING) {
|
||||
dispatch(hidePendingRecordingNotification(mode));
|
||||
}
|
||||
|
||||
if (updatedSessionData.status === ON
|
||||
&& (!oldSessionData || oldSessionData.status !== ON)) {
|
||||
if (initiator) {
|
||||
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
|
||||
switch (newStatus) {
|
||||
case WAITING_IN_QUEUE:
|
||||
dispatch(showWaitingInQueueRecordingNotification(mode));
|
||||
break;
|
||||
case QUEUE_LEFT:
|
||||
dispatch(showQueueLeftRecordingNotification(mode));
|
||||
break;
|
||||
case PENDING:
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
break;
|
||||
case ON: {
|
||||
if (initiator) {
|
||||
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
|
||||
|
||||
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
|
||||
} else if (typeof recordingLimit === 'object') {
|
||||
// Show notification with additional information to the initiator.
|
||||
dispatch(showRecordingLimitNotification(mode));
|
||||
}
|
||||
|
||||
|
||||
sendAnalytics(createRecordingEvent('start', mode));
|
||||
|
||||
if (disableRecordAudioNotification) {
|
||||
break;
|
||||
}
|
||||
|
||||
let soundID;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
soundID = RECORDING_ON_SOUND_ID;
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
soundID = LIVE_STREAMING_ON_SOUND_ID;
|
||||
}
|
||||
|
||||
if (soundID) {
|
||||
dispatch(playSound(soundID));
|
||||
}
|
||||
} else if (updatedSessionData.status === OFF
|
||||
&& (!oldSessionData || oldSessionData.status !== OFF)) {
|
||||
dispatch(showStoppedRecordingNotification(
|
||||
mode, terminator && getParticipantDisplayName(getState, terminator.getId())));
|
||||
let duration = 0, soundOff, soundOn;
|
||||
|
||||
if (oldSessionData && oldSessionData.timestamp) {
|
||||
duration
|
||||
= (Date.now() / 1000) - oldSessionData.timestamp;
|
||||
}
|
||||
sendAnalytics(createRecordingEvent('stop', mode, duration));
|
||||
|
||||
if (disableRecordAudioNotification) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
soundOff = RECORDING_OFF_SOUND_ID;
|
||||
soundOn = RECORDING_ON_SOUND_ID;
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
soundOff = LIVE_STREAMING_OFF_SOUND_ID;
|
||||
soundOn = LIVE_STREAMING_ON_SOUND_ID;
|
||||
}
|
||||
|
||||
if (soundOff && soundOn) {
|
||||
dispatch(stopSound(soundOn));
|
||||
dispatch(playSound(soundOff));
|
||||
}
|
||||
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
|
||||
} else if (typeof recordingLimit === 'object') {
|
||||
// Show notification with additional information to the initiator.
|
||||
dispatch(showRecordingLimitNotification(mode));
|
||||
}
|
||||
|
||||
|
||||
sendAnalytics(createRecordingEvent('start', mode));
|
||||
|
||||
if (disableRecordAudioNotification) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let soundID;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
soundID = RECORDING_ON_SOUND_ID;
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
soundID = LIVE_STREAMING_ON_SOUND_ID;
|
||||
}
|
||||
|
||||
if (soundID) {
|
||||
dispatch(playSound(soundID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OFF: {
|
||||
dispatch(showStoppedRecordingNotification(
|
||||
mode, terminator && getParticipantDisplayName(getState, terminator.getId())));
|
||||
let duration = 0, soundOff, soundOn;
|
||||
|
||||
if (oldSessionData && oldSessionData.timestamp) {
|
||||
duration
|
||||
= (Date.now() / 1000) - oldSessionData.timestamp;
|
||||
}
|
||||
sendAnalytics(createRecordingEvent('stop', mode, duration));
|
||||
|
||||
if (disableRecordAudioNotification) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
soundOff = RECORDING_OFF_SOUND_ID;
|
||||
soundOn = RECORDING_ON_SOUND_ID;
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
soundOff = LIVE_STREAMING_OFF_SOUND_ID;
|
||||
soundOn = LIVE_STREAMING_ON_SOUND_ID;
|
||||
}
|
||||
|
||||
if (soundOff && soundOn) {
|
||||
dispatch(stopSound(soundOn));
|
||||
dispatch(playSound(soundOff));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
CLEAR_RECORDING_SESSIONS,
|
||||
RECORDING_SESSION_UPDATED,
|
||||
SET_PENDING_RECORDING_NOTIFICATION_UID,
|
||||
SET_STREAM_KEY
|
||||
SET_STREAM_KEY,
|
||||
SET_WAITING_IN_RECORDING_NOTIFICATION_UID
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
pendingNotificationUids: {},
|
||||
sessionDatas: []
|
||||
sessionDatas: [],
|
||||
waitingInQueueNotificationUids: {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -56,6 +58,20 @@ ReducerRegistry.register(STORE_NAME,
|
||||
streamKey: action.streamKey
|
||||
};
|
||||
|
||||
case SET_WAITING_IN_RECORDING_NOTIFICATION_UID: {
|
||||
const waitingInQueueNotificationUids = {
|
||||
...state.waitingInQueueNotificationUids
|
||||
};
|
||||
|
||||
waitingInQueueNotificationUids[action.streamType] = action.uid;
|
||||
|
||||
return {
|
||||
...state,
|
||||
waitingInQueueNotificationUids
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -71,12 +87,12 @@ ReducerRegistry.register(STORE_NAME,
|
||||
*/
|
||||
function _updateSessionDatas(sessionDatas, newSessionData) {
|
||||
const hasExistingSessionData = sessionDatas.find(
|
||||
sessionData => sessionData.id === newSessionData.id);
|
||||
sessionData => sessionData.id === newSessionData.id || sessionData.queueID === newSessionData.queueID);
|
||||
let newSessionDatas;
|
||||
|
||||
if (hasExistingSessionData) {
|
||||
newSessionDatas = sessionDatas.map(sessionData => {
|
||||
if (sessionData.id === newSessionData.id) {
|
||||
if (sessionData.id === newSessionData.id || sessionData.queueID === newSessionData.queueID) {
|
||||
return {
|
||||
...newSessionData
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
local jibri_queue_component
|
||||
= module:get_option_string(
|
||||
"jibri_queue_component", "jibriqueue"..module.host);
|
||||
|
||||
module:add_identity("component", "jibri-queue", jibri_queue_component);
|
||||
@@ -1,559 +0,0 @@
|
||||
local st = require "util.stanza";
|
||||
local jid = require "util.jid";
|
||||
local http = require "net.http";
|
||||
local json = require "cjson";
|
||||
local inspect = require('inspect');
|
||||
local socket = require "socket";
|
||||
local uuid_gen = require "util.uuid".generate;
|
||||
local jwt = require "luajwtjitsi";
|
||||
local it = require "util.iterators";
|
||||
local neturl = require "net.url";
|
||||
local parse = neturl.parseQuery;
|
||||
|
||||
local get_room_from_jid = module:require "util".get_room_from_jid;
|
||||
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
|
||||
local is_healthcheck_room = module:require "util".is_healthcheck_room;
|
||||
local room_jid_split_subdomain = module:require "util".room_jid_split_subdomain;
|
||||
local internal_room_jid_match_rewrite = module:require "util".internal_room_jid_match_rewrite;
|
||||
local async_handler_wrapper = module:require "util".async_handler_wrapper;
|
||||
|
||||
-- this basically strips the domain from the conference.domain address
|
||||
local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")();
|
||||
if parentHostName == nil then
|
||||
log("error", "Failed to start - unable to get parent hostname");
|
||||
return;
|
||||
end
|
||||
|
||||
local parentCtx = module:context(parentHostName);
|
||||
if parentCtx == nil then
|
||||
log("error",
|
||||
"Failed to start - unable to get parent context for host: %s",
|
||||
tostring(parentHostName));
|
||||
return;
|
||||
end
|
||||
local token_util = module:require "token/util".new(parentCtx);
|
||||
|
||||
local ASAPKeyServer;
|
||||
local ASAPKeyPath;
|
||||
local ASAPKeyId;
|
||||
local ASAPIssuer;
|
||||
local ASAPAudience;
|
||||
local ASAPAcceptedIssuers;
|
||||
local ASAPAcceptedAudiences;
|
||||
local ASAPTTL;
|
||||
local ASAPTTL_THRESHOLD;
|
||||
local ASAPKey;
|
||||
local JibriRegion;
|
||||
local disableTokenVerification;
|
||||
local muc_component_host;
|
||||
local external_api_url;
|
||||
local jwtKeyCacheSize;
|
||||
local jwtKeyCache;
|
||||
|
||||
local function load_config()
|
||||
ASAPKeyServer = module:get_option_string("asap_key_server");
|
||||
|
||||
if ASAPKeyServer then
|
||||
module:log("debug", "ASAP Public Key URL %s", ASAPKeyServer);
|
||||
token_util:set_asap_key_server(ASAPKeyServer);
|
||||
end
|
||||
|
||||
ASAPKeyPath
|
||||
= module:get_option_string("asap_key_path", '/etc/prosody/certs/asap.key');
|
||||
|
||||
ASAPKeyId
|
||||
= module:get_option_string("asap_key_id", 'jitsi');
|
||||
|
||||
ASAPIssuer
|
||||
= module:get_option_string("asap_issuer", 'jitsi');
|
||||
|
||||
ASAPAudience
|
||||
= module:get_option_string("asap_audience", 'jibri-queue');
|
||||
|
||||
ASAPAcceptedIssuers
|
||||
= module:get_option_array('asap_accepted_issuers',{'jibri-queue'});
|
||||
module:log("debug", "ASAP Accepted Issuers %s", ASAPAcceptedIssuers);
|
||||
token_util:set_asap_accepted_issuers(ASAPAcceptedIssuers);
|
||||
|
||||
ASAPAcceptedAudiences
|
||||
= module:get_option_array('asap_accepted_audiences',{'*'});
|
||||
module:log("debug", "ASAP Accepted Audiences %s", ASAPAcceptedAudiences);
|
||||
token_util:set_asap_accepted_audiences(ASAPAcceptedAudiences);
|
||||
|
||||
-- do not require room to be set on tokens for jibri queue
|
||||
token_util:set_asap_require_room_claim(false);
|
||||
|
||||
ASAPTTL
|
||||
= module:get_option_number("asap_ttl", 3600);
|
||||
|
||||
ASAPTTL_THRESHOLD
|
||||
= module:get_option_number("asap_ttl_threshold", 600);
|
||||
|
||||
queueServiceURL
|
||||
= module:get_option_string("jibri_queue_url");
|
||||
|
||||
JibriRegion
|
||||
= module:get_option_string("jibri_region", 'default');
|
||||
|
||||
-- option to enable/disable token verifications
|
||||
disableTokenVerification
|
||||
= module:get_option_boolean("disable_jibri_queue_token_verification", false);
|
||||
|
||||
muc_component_host
|
||||
= module:get_option_string("muc_component");
|
||||
|
||||
external_api_url = module:get_option_string("external_api_url",tostring(parentHostName));
|
||||
module:log("debug", "External advertised API URL", external_api_url);
|
||||
|
||||
|
||||
-- TODO: Figure out a less arbitrary default cache size.
|
||||
jwtKeyCacheSize
|
||||
= module:get_option_number("jwt_pubkey_cache_size", 128);
|
||||
jwtKeyCache = require"util.cache".new(jwtKeyCacheSize);
|
||||
|
||||
if queueServiceURL == nil then
|
||||
log("error", "No jibri_queue_url specified. No service to contact!");
|
||||
return;
|
||||
end
|
||||
|
||||
if muc_component_host == nil then
|
||||
log("error", "No muc_component specified. No muc to operate on for jibri queue!");
|
||||
return;
|
||||
end
|
||||
|
||||
-- Read ASAP key once on module startup
|
||||
local f = io.open(ASAPKeyPath, "r");
|
||||
if f then
|
||||
ASAPKey = f:read("*all");
|
||||
f:close();
|
||||
if not ASAPKey then
|
||||
module:log("warn", "No ASAP Key read from %s, disabling jibri queue component plugin", ASAPKeyPath);
|
||||
return
|
||||
end
|
||||
else
|
||||
module:log("warn", "Error reading ASAP Key %s, disabling jibri queue component plugin", ASAPKeyPath);
|
||||
return
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
local function reload_config()
|
||||
module:log("info", "Reloading configuration for jibri queue component");
|
||||
local config_success = load_config();
|
||||
|
||||
-- clear ASAP public key cache on config reload
|
||||
token_util:clear_asap_cache();
|
||||
|
||||
if not config_success then
|
||||
log("error", "Unsuccessful reconfiguration, jibri queue component may misbehave");
|
||||
end
|
||||
end
|
||||
|
||||
local config_success = load_config();
|
||||
|
||||
if not config_success then
|
||||
log("error", "Unsuccessful configuration step, jibri queue component disabled")
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
local http_headers = {
|
||||
["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")",
|
||||
["Content-Type"] = "application/json"
|
||||
};
|
||||
|
||||
-- we use async to detect Prosody 0.10 and earlier
|
||||
local have_async = pcall(require, "util.async");
|
||||
if not have_async then
|
||||
module:log("warn", "conference duration will not work with Prosody version 0.10 or less.");
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
log("info", "Starting jibri queue handling for %s", muc_component_host);
|
||||
|
||||
local function round(num, numDecimalPlaces)
|
||||
local mult = 10^(numDecimalPlaces or 0)
|
||||
return math.floor(num * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
local function generateToken(audience)
|
||||
audience = audience or ASAPAudience
|
||||
local t = os.time()
|
||||
local err
|
||||
local exp_key = 'asap_exp.'..audience
|
||||
local token_key = 'asap_token.'..audience
|
||||
local exp = jwtKeyCache:get(exp_key)
|
||||
local token = jwtKeyCache:get(token_key)
|
||||
|
||||
--if we find a token and it isn't too far from expiry, then use it
|
||||
if token ~= nil and exp ~= nil then
|
||||
exp = tonumber(exp)
|
||||
if (exp - t) > ASAPTTL_THRESHOLD then
|
||||
return token
|
||||
end
|
||||
end
|
||||
|
||||
--expiry is the current time plus TTL
|
||||
exp = t + ASAPTTL
|
||||
local payload = {
|
||||
iss = ASAPIssuer,
|
||||
aud = audience,
|
||||
nbf = t,
|
||||
exp = exp,
|
||||
}
|
||||
|
||||
-- encode
|
||||
local alg = "RS256"
|
||||
token, err = jwt.encode(payload, ASAPKey, alg, {kid = ASAPKeyId})
|
||||
if not err then
|
||||
token = 'Bearer '..token
|
||||
jwtKeyCache:set(exp_key,exp)
|
||||
jwtKeyCache:set(token_key,token)
|
||||
return token
|
||||
else
|
||||
return ''
|
||||
end
|
||||
end
|
||||
|
||||
local function sendIq(participant,action,requestId,time,position,token)
|
||||
local iqId = uuid_gen();
|
||||
local from = module:get_host();
|
||||
local outStanza = st.iq({type = 'set', from = from, to = participant, id = iqId}):tag("jibri-queue",
|
||||
{ xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId, action = action });
|
||||
|
||||
if token then
|
||||
outStanza:tag("token"):text(token):up()
|
||||
end
|
||||
if time then
|
||||
outStanza:tag("time"):text(tostring(time)):up()
|
||||
end
|
||||
if position then
|
||||
outStanza:tag("position"):text(tostring(position)):up()
|
||||
end
|
||||
|
||||
module:send(outStanza);
|
||||
end
|
||||
|
||||
local function cb(content_, code_, response_, request_)
|
||||
if code_ == 200 or code_ == 204 then
|
||||
module:log("debug", "URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s",
|
||||
code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
|
||||
else
|
||||
module:log("warn", "URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s",
|
||||
code_, content_, inspect(request_), inspect(response_));
|
||||
end
|
||||
end
|
||||
|
||||
local function sendEvent(type,room_address,participant,requestId,replyIq,replyError)
|
||||
local event_ts = round(socket.gettime()*1000);
|
||||
local node, host, resource, target_subdomain = room_jid_split_subdomain(room_address);
|
||||
local room_param = '';
|
||||
if target_subdomain then
|
||||
room_param = target_subdomain..'/'..node;
|
||||
else
|
||||
room_param = node;
|
||||
end
|
||||
|
||||
local out_event = {
|
||||
["conference"] = room_address,
|
||||
["roomParam"] = room_param,
|
||||
["eventType"] = type,
|
||||
["participant"] = participant,
|
||||
["externalApiUrl"] = external_api_url.."/jibriqueue/update",
|
||||
["requestId"] = requestId,
|
||||
["region"] = JibriRegion,
|
||||
}
|
||||
module:log("debug","Sending event %s",inspect(out_event));
|
||||
|
||||
local headers = http_headers or {}
|
||||
headers['Authorization'] = generateToken()
|
||||
|
||||
module:log("debug","Sending headers %s",inspect(headers));
|
||||
local requestURL = queueServiceURL.."/job/recording"
|
||||
if type=="LeaveQueue" then
|
||||
requestURL = requestURL .."/cancel"
|
||||
end
|
||||
local request = http.request(requestURL, {
|
||||
headers = headers,
|
||||
method = "POST",
|
||||
body = json.encode(out_event)
|
||||
}, function (content_, code_, response_, request_)
|
||||
if code_ == 200 or code_ == 204 then
|
||||
module:log("debug", "URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s",
|
||||
code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
|
||||
if (replyIq) then
|
||||
module:log("debug", "sending reply IQ %s",inspect(replyIq));
|
||||
module:send(replyIq);
|
||||
end
|
||||
else
|
||||
module:log("warn", "URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s",
|
||||
code_, content_, inspect(request_), inspect(response_));
|
||||
if (replyError) then
|
||||
module:log("warn", "sending reply error IQ %s",inspect(replyError));
|
||||
module:send(replyError);
|
||||
end
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
function clearRoomQueueByOccupant(room, occupant)
|
||||
room.jibriQueue[occupant.jid] = nil;
|
||||
end
|
||||
|
||||
function addRoomQueueByOccupant(room, occupant, requestId)
|
||||
room.jibriQueue[occupant.jid] = requestId;
|
||||
end
|
||||
|
||||
-- receives iq from client currently connected to the room
|
||||
function on_iq(event)
|
||||
local requestId;
|
||||
-- Check the type of the incoming stanza to avoid loops:
|
||||
if event.stanza.attr.type == "error" then
|
||||
return; -- We do not want to reply to these, so leave.
|
||||
end
|
||||
if event.stanza.attr.to == module:get_host() then
|
||||
if event.stanza.attr.type == "set" then
|
||||
local reply = st.reply(event.stanza);
|
||||
local replyError = st.error_reply(event.stanza,'cancel','internal-server-error',"Queue Server Error");
|
||||
|
||||
local jibriQueue
|
||||
= event.stanza:get_child('jibri-queue', 'http://jitsi.org/protocol/jibri-queue');
|
||||
if jibriQueue then
|
||||
module:log("debug", "Received Jibri Queue Request: %s ",inspect(jibriQueue));
|
||||
|
||||
local roomAddress = jibriQueue.attr.room;
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
|
||||
|
||||
if not room then
|
||||
module:log("warn", "No room found %s", roomAddress);
|
||||
return false;
|
||||
end
|
||||
|
||||
local from = event.stanza.attr.from;
|
||||
|
||||
local occupant = room:get_occupant_by_real_jid(from);
|
||||
if not occupant then
|
||||
module:log("warn", "No occupant %s found for %s", from, roomAddress);
|
||||
return false;
|
||||
end
|
||||
|
||||
local action = jibriQueue.attr.action;
|
||||
if action == 'join' then
|
||||
-- join action, so send event out
|
||||
requestId = uuid_gen();
|
||||
module:log("debug","Received join queue request for jid %s occupant %s requestId %s",roomAddress,occupant.jid,requestId);
|
||||
|
||||
-- now handle new jibri queue message
|
||||
addRoomQueueByOccupant(room, occupant, requestId);
|
||||
reply:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
|
||||
replyError:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
|
||||
|
||||
module:log("debug","Sending JoinQueue event for jid %s occupant %s reply %s",roomAddress,occupant.jid,inspect(reply));
|
||||
sendEvent('JoinQueue',roomAddress,occupant.jid,requestId,reply,replyError);
|
||||
end
|
||||
if action == 'leave' then
|
||||
requestId = jibriQueue.attr.requestId;
|
||||
module:log("debug","Received leave queue request for jid %s occupant %s requestId %s",roomAddress,occupant.jid,requestId);
|
||||
|
||||
-- TODO: check that requestId is the same as cached value
|
||||
clearRoomQueueByOccupant(room, occupant);
|
||||
reply:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
|
||||
replyError:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
|
||||
|
||||
module:log("debug","Sending LeaveQueue event for jid %s occupant %s reply %s",roomAddress,occupant.jid,inspect(reply));
|
||||
sendEvent('LeaveQueue',roomAddress,occupant.jid,requestId,reply,replyError);
|
||||
end
|
||||
else
|
||||
module:log("warn","Jibri Queue Stanza missing child %s",inspect(event.stanza))
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- create recorder queue cache for the room
|
||||
function room_created(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
room.jibriQueue = {};
|
||||
end
|
||||
|
||||
-- Conference ended, clear all queue cache jids
|
||||
function room_destroyed(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
for jid, x in pairs(room.jibriQueue) do
|
||||
if x then
|
||||
sendEvent('LeaveQueue',internal_room_jid_match_rewrite(room.jid),jid,x);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Occupant left remove it from the queue if it joined the queue
|
||||
function occupant_leaving(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant = event.occupant;
|
||||
local requestId = room.jibriQueue[occupant.jid];
|
||||
-- check if user has cached queue request
|
||||
if requestId then
|
||||
-- remove occupant from queue cache, signal backend
|
||||
room.jibriQueue[occupant.jid] = nil;
|
||||
sendEvent('LeaveQueue',internal_room_jid_match_rewrite(room.jid),occupant.jid,requestId);
|
||||
end
|
||||
end
|
||||
|
||||
module:hook("iq/host", on_iq);
|
||||
|
||||
-- executed on every host added internally in prosody, including components
|
||||
function process_host(host)
|
||||
if host == muc_component_host then -- the conference muc component
|
||||
module:log("debug","Hook to muc events on %s", host);
|
||||
|
||||
local muc_module = module:context(host);
|
||||
muc_module:hook("muc-room-created", room_created, -1);
|
||||
-- muc_module:hook("muc-occupant-joined", occupant_joined, -1);
|
||||
muc_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
|
||||
muc_module:hook("muc-room-destroyed", room_destroyed, -1);
|
||||
end
|
||||
end
|
||||
|
||||
if prosody.hosts[muc_component_host] == nil then
|
||||
module:log("debug","No muc component found, will listen for it: %s", muc_component_host)
|
||||
|
||||
-- when a host or component is added
|
||||
prosody.events.add_handler("host-activated", process_host);
|
||||
else
|
||||
process_host(muc_component_host);
|
||||
end
|
||||
|
||||
module:log("info", "Loading jibri_queue_component");
|
||||
|
||||
--- Verifies room name, domain name with the values in the token
|
||||
-- @param token the token we received
|
||||
-- @param room_name the room name
|
||||
-- @param group name of the group (optional)
|
||||
-- @param session the session to use for storing token specific fields
|
||||
-- @return true if values are ok or false otherwise
|
||||
function verify_token(token, room_jid, session)
|
||||
if disableTokenVerification then
|
||||
return true;
|
||||
end
|
||||
|
||||
-- if not disableTokenVerification and we do not have token
|
||||
-- stop here, cause the main virtual host can have guest access enabled
|
||||
-- (allowEmptyToken = true) and we will allow access to rooms info without
|
||||
-- a token
|
||||
if token == nil then
|
||||
log("warn", "no token provided");
|
||||
return false;
|
||||
end
|
||||
|
||||
session.auth_token = token;
|
||||
local verified, reason, message = token_util:process_and_verify_token(session);
|
||||
if not verified then
|
||||
log("warn", "not a valid token %s: %s", tostring(reason), tostring(message));
|
||||
log("debug", "invalid token %s", token);
|
||||
return false;
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
--- Handles request for updating jibri queue status
|
||||
-- @param event the http event, holds the request query
|
||||
-- @return GET response, containing a json with response details
|
||||
function handle_update_jibri_queue(event)
|
||||
local body = json.decode(event.request.body);
|
||||
|
||||
module:log("debug","Update Jibri Queue Event Received: %s",inspect(body));
|
||||
|
||||
local token = event.request.headers["authorization"];
|
||||
if not token then
|
||||
token = ''
|
||||
else
|
||||
local prefixStart, prefixEnd = token:find("Bearer ");
|
||||
if prefixStart ~= 1 then
|
||||
module:log("error", "REST event: Invalid authorization header format. The header must start with the string 'Bearer '");
|
||||
return { status_code = 403; };
|
||||
end
|
||||
token = token:sub(prefixEnd + 1);
|
||||
end
|
||||
|
||||
local user_jid = body["participant"];
|
||||
local roomAddress = body["conference"];
|
||||
local userJWT = body["token"];
|
||||
local action = body["action"];
|
||||
local time = body["time"];
|
||||
local position = body["position"];
|
||||
local requestId = body["requestId"];
|
||||
|
||||
if not action then
|
||||
if userJWT then
|
||||
action = 'token';
|
||||
else
|
||||
action = 'info';
|
||||
end
|
||||
end
|
||||
|
||||
local room_jid = room_jid_match_rewrite(roomAddress);
|
||||
|
||||
if not verify_token(token, room_jid, {}) then
|
||||
log("error", "REST event: Invalid token for room %s to route action %s for requestId %s", roomAddress, action, requestId);
|
||||
return { status_code = 403; };
|
||||
end
|
||||
|
||||
local room = get_room_from_jid(room_jid);
|
||||
if (not room) then
|
||||
log("error", "REST event: no room found %s to route action %s for requestId %s", roomAddress, action, requestId);
|
||||
return { status_code = 404; };
|
||||
end
|
||||
|
||||
local occupant = room:get_occupant_by_real_jid(user_jid);
|
||||
if not occupant then
|
||||
log("warn", "REST event: No occupant %s found for %s to route action %s for requestId %s", user_jid, roomAddress, action, requestId);
|
||||
return { status_code = 404; };
|
||||
end
|
||||
|
||||
if not room.jibriQueue[occupant.jid] then
|
||||
log("warn", "REST event: No queue request found for occupant %s in conference %s to route action %s for requestId %s",occupant.jid,room.jid, action, requestId)
|
||||
return { status_code = 404; };
|
||||
end
|
||||
|
||||
if not requestId then
|
||||
requestId = room.jibriQueue[occupant.jid];
|
||||
end
|
||||
|
||||
if action == 'token' and userJWT then
|
||||
log("debug", "REST event: Token received for occupant %s in conference %s requestId %s, clearing room queue");
|
||||
clearRoomQueueByOccupant(room, occupant);
|
||||
end
|
||||
|
||||
log("debug", "REST event: Sending update for occupant %s in conference %s to route action %s for requestId %s",occupant.jid,room.jid, action, requestId);
|
||||
sendIq(occupant.jid,action,requestId,time,position,userJWT);
|
||||
return { status_code = 200; };
|
||||
end
|
||||
|
||||
module:depends("http");
|
||||
module:provides("http", {
|
||||
default_path = "/";
|
||||
name = "jibriqueue";
|
||||
route = {
|
||||
["POST /jibriqueue/update"] = function (event) return async_handler_wrapper(event,handle_update_jibri_queue) end;
|
||||
};
|
||||
});
|
||||
|
||||
module:hook_global('config-reloaded', reload_config);
|
||||
@@ -132,7 +132,7 @@ function filter_stanza(stanza)
|
||||
|
||||
-- check is an owner, only owners can receive the presence
|
||||
local room = main_muc_service.get_room_from_jid(jid_bare(node .. '@' .. main_muc_component_config));
|
||||
if not room or room.get_affiliation(room, stanza.attr.to) == 'owner' then
|
||||
if room.get_affiliation(room, stanza.attr.to) == 'owner' then
|
||||
return stanza;
|
||||
end
|
||||
|
||||
@@ -159,11 +159,6 @@ function attach_lobby_room(room)
|
||||
local lobby_room_jid = node .. '@' .. lobby_muc_component_config;
|
||||
if not lobby_muc_service.get_room_from_jid(lobby_room_jid) then
|
||||
local new_room = lobby_muc_service.create_room(lobby_room_jid);
|
||||
-- set persistent the lobby room to avoid it to be destroyed
|
||||
-- there are cases like when selecting new moderator after the current one leaves
|
||||
-- which can leave the room with no occupants and it will be destroyed and we want to
|
||||
-- avoid lobby destroy while it is enabled
|
||||
new_room:set_persistent(true);
|
||||
module:log("debug","Lobby room jid = %s created",lobby_room_jid);
|
||||
new_room.main_room = room;
|
||||
room._data.lobbyroom = new_room;
|
||||
@@ -173,18 +168,6 @@ function attach_lobby_room(room)
|
||||
return false
|
||||
end
|
||||
|
||||
-- destroys lobby room for the supplied main room
|
||||
function destroy_lobby_room(room, newjid, message)
|
||||
if not message then
|
||||
message = 'Lobby room closed.';
|
||||
end
|
||||
if room and room._data.lobbyroom then
|
||||
room._data.lobbyroom:set_persistent(false);
|
||||
room._data.lobbyroom:destroy(newjid, message);
|
||||
room._data.lobbyroom = nil;
|
||||
end
|
||||
end
|
||||
|
||||
-- process a host module directly if loaded or hooks to wait for its load
|
||||
function process_host_module(name, callback)
|
||||
local function process_host(host)
|
||||
@@ -297,14 +280,16 @@ process_host_module(main_muc_component_config, function(host_module, host)
|
||||
notify_lobby_enabled(room, actor, true);
|
||||
end
|
||||
elseif room._data.lobbyroom then
|
||||
destroy_lobby_room(room, room.jid);
|
||||
room._data.lobbyroom:destroy(room.jid, 'Lobby room closed.');
|
||||
room._data.lobbyroom = nil;
|
||||
notify_lobby_enabled(room, actor, false);
|
||||
end
|
||||
end);
|
||||
host_module:hook('muc-room-destroyed',function(event)
|
||||
local room = event.room;
|
||||
if room._data.lobbyroom then
|
||||
destroy_lobby_room(room, nil);
|
||||
room._data.lobbyroom:destroy(nil, 'Lobby room closed.');
|
||||
room._data.lobbyroom = nil;
|
||||
end
|
||||
end);
|
||||
host_module:hook('muc-disco#info', function (event)
|
||||
@@ -414,12 +399,7 @@ function handle_create_lobby(event)
|
||||
attach_lobby_room(room)
|
||||
end
|
||||
|
||||
function handle_destroy_lobby(event)
|
||||
destroy_lobby_room(event.room, event.newjid, event.message);
|
||||
end
|
||||
|
||||
module:hook_global('bosh-session', update_session);
|
||||
module:hook_global('websocket-session', update_session);
|
||||
module:hook_global('config-reloaded', load_config);
|
||||
module:hook_global('create-lobby-room', handle_create_lobby);
|
||||
module:hook_global('destroy-lobby-room', handle_destroy_lobby);
|
||||
|
||||
@@ -93,8 +93,6 @@ function Util.new(module)
|
||||
--array of accepted audiences: by default only includes our appId
|
||||
self.acceptedAudiences = module:get_option_array('asap_accepted_audiences',{'*'})
|
||||
|
||||
self.requireRoomClaim = module:get_option_boolean('asap_require_room_claim', true);
|
||||
|
||||
if self.asapKeyServer and not have_async then
|
||||
module:log("error", "requires a version of Prosody with util.async");
|
||||
return nil;
|
||||
@@ -104,23 +102,7 @@ function Util.new(module)
|
||||
end
|
||||
|
||||
function Util:set_asap_key_server(asapKeyServer)
|
||||
self.asapKeyServer = asapKeyServer;
|
||||
end
|
||||
|
||||
function Util:set_asap_accepted_issuers(acceptedIssuers)
|
||||
self.acceptedIssuers = acceptedIssuers;
|
||||
end
|
||||
|
||||
function Util:set_asap_accepted_audiences(acceptedAudiences)
|
||||
self.acceptedAudiences = acceptedAudiences;
|
||||
end
|
||||
|
||||
function Util:set_asap_require_room_claim(checkRoom)
|
||||
self.requireRoomClaim = checkRoom;
|
||||
end
|
||||
|
||||
function Util:clear_asap_cache()
|
||||
self.cache = require"util.cache".new(cacheSize);
|
||||
self.asapKeyServer = asapKeyServer
|
||||
end
|
||||
|
||||
--- Returns the public key by keyID
|
||||
@@ -132,41 +114,18 @@ function Util:get_public_key(keyId)
|
||||
-- If the key is not found in the cache.
|
||||
module:log("debug", "Cache miss for key: "..keyId);
|
||||
local code;
|
||||
local timeout_occurred;
|
||||
local wait, done = async.waiter();
|
||||
|
||||
local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
|
||||
|
||||
local function cb(content_, code_, response_, request_)
|
||||
if timeout_occurred == nil then
|
||||
content, code = content_, code_;
|
||||
if code == 200 or code == 204 then
|
||||
self.cache:set(keyId, content);
|
||||
else
|
||||
module:log("warn", "Error on public key request: Code %s, Content %s",
|
||||
code_, content_);
|
||||
end
|
||||
done();
|
||||
content, code = content_, code_;
|
||||
if code == 200 or code == 204 then
|
||||
self.cache:set(keyId, content);
|
||||
else
|
||||
module:log("warn", "public key reply delivered after timeout from: %s",keyurl);
|
||||
module:log("warn", "Error on public key request: Code %s, Content %s",
|
||||
code_, content_);
|
||||
end
|
||||
done();
|
||||
end
|
||||
|
||||
-- TODO: Is the done() call racey? Can we cancel this if the request
|
||||
-- succeedes?
|
||||
local function cancel()
|
||||
-- TODO: This check is racey. Not likely to be a problem, but we should
|
||||
-- still stick a mutex on content / code at some point.
|
||||
if code == nil then
|
||||
timeout_occurred = true;
|
||||
module:log("warn", "Timeout %s seconds fetching public key from: %s",http_timeout,keyurl);
|
||||
if http.destroy_request ~= nil then
|
||||
http.destroy_request(request);
|
||||
end
|
||||
done();
|
||||
end
|
||||
end
|
||||
|
||||
local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
|
||||
module:log("debug", "Fetching public key from: "..keyurl);
|
||||
|
||||
-- We hash the key ID to work around some legacy behavior and make
|
||||
@@ -177,6 +136,19 @@ function Util:get_public_key(keyId)
|
||||
method = "GET"
|
||||
}, cb);
|
||||
|
||||
-- TODO: Is the done() call racey? Can we cancel this if the request
|
||||
-- succeedes?
|
||||
local function cancel()
|
||||
-- TODO: This check is racey. Not likely to be a problem, but we should
|
||||
-- still stick a mutex on content / code at some point.
|
||||
if code == nil then
|
||||
-- no longer present in prosody 0.11, so check before calling
|
||||
if http.destroy_request ~= nil then
|
||||
http.destroy_request(request);
|
||||
end
|
||||
done();
|
||||
end
|
||||
end
|
||||
timer.add_task(http_timeout, cancel);
|
||||
wait();
|
||||
|
||||
@@ -197,10 +169,6 @@ end
|
||||
-- @param 'acceptedIssuers' list of issuers to check
|
||||
-- @return nil and error string or true for accepted claim
|
||||
function Util:verify_issuer(issClaim, acceptedIssuers)
|
||||
if not acceptedIssuers then
|
||||
acceptedIssuers = self.acceptedIssuers
|
||||
end
|
||||
module:log("debug","verify_issuer claim: %s against accepted: %s",issClaim, acceptedIssuers);
|
||||
for i, iss in ipairs(acceptedIssuers) do
|
||||
if issClaim == iss then
|
||||
--claim matches an accepted issuer so return success
|
||||
@@ -215,7 +183,6 @@ end
|
||||
-- @param 'aud' claim from the token to verify
|
||||
-- @return nil and error string or true for accepted claim
|
||||
function Util:verify_audience(audClaim)
|
||||
module:log("debug","verify_audience claim: %s against accepted: %s",audClaim, self.acceptedAudiences);
|
||||
for i, aud in ipairs(self.acceptedAudiences) do
|
||||
if aud == '*' then
|
||||
--* indicates to accept any audience in the claims so return success
|
||||
@@ -256,11 +223,9 @@ function Util:verify_token(token, secret, acceptedIssuers)
|
||||
return nil, issCheckErr;
|
||||
end
|
||||
|
||||
if self.requireRoomClaim then
|
||||
local roomClaim = claims["room"];
|
||||
if roomClaim == nil then
|
||||
return nil, "'room' claim is missing";
|
||||
end
|
||||
local roomClaim = claims["room"];
|
||||
if roomClaim == nil then
|
||||
return nil, "'room' claim is missing";
|
||||
end
|
||||
|
||||
local audClaim = claims["aud"];
|
||||
|
||||
@@ -18,20 +18,14 @@ local escaped_muc_domain_prefix = muc_domain_prefix:gsub("%p", "%%%1");
|
||||
local target_subdomain_pattern
|
||||
= "^"..escaped_muc_domain_prefix..".([^%.]+)%."..escaped_muc_domain_base;
|
||||
|
||||
-- Utility function to split room JID to include room name and subdomain
|
||||
local function room_jid_split_subdomain(room_jid)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
local target_subdomain = host and host:match(target_subdomain_pattern);
|
||||
return node, host, resource, target_subdomain
|
||||
end
|
||||
|
||||
--- Utility function to check and convert a room JID from
|
||||
-- virtual room1@muc.foo.example.com to real [foo]room1@muc.example.com
|
||||
-- @param room_jid the room jid to match and rewrite if needed
|
||||
-- @return returns room jid [foo]room1@muc.example.com when it has subdomain
|
||||
-- otherwise room1@muc.example.com(the room_jid value untouched)
|
||||
local function room_jid_match_rewrite(room_jid)
|
||||
local node, host, resource, target_subdomain = room_jid_split_subdomain(room_jid);
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
local target_subdomain = host and host:match(target_subdomain_pattern);
|
||||
if not target_subdomain then
|
||||
module:log("debug", "No need to rewrite out 'to' %s", room_jid);
|
||||
return room_jid;
|
||||
@@ -44,23 +38,6 @@ local function room_jid_match_rewrite(room_jid)
|
||||
return room_jid
|
||||
end
|
||||
|
||||
local function internal_room_jid_match_rewrite(room_jid)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
if host ~= muc_domain or not node then
|
||||
module:log("debug", "No need to rewrite %s (not from the MUC host)", room_jid);
|
||||
return room_jid;
|
||||
end
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
|
||||
if not (target_node and target_subdomain) then
|
||||
module:log("debug", "Not rewriting... unexpected node format: %s", node);
|
||||
return room_jid;
|
||||
end
|
||||
-- Ok, rewrite room_jid address to pretty format
|
||||
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
module:log("debug", "Rewrote to %s", room_jid);
|
||||
return room_jid
|
||||
end
|
||||
|
||||
--- Finds and returns room by its jid
|
||||
-- @param room_jid the room jid to search in the muc component
|
||||
@@ -214,7 +191,5 @@ return {
|
||||
get_room_from_jid = get_room_from_jid;
|
||||
async_handler_wrapper = async_handler_wrapper;
|
||||
room_jid_match_rewrite = room_jid_match_rewrite;
|
||||
room_jid_split_subdomain = room_jid_split_subdomain;
|
||||
internal_room_jid_match_rewrite = internal_room_jid_match_rewrite;
|
||||
update_presence_identity = update_presence_identity;
|
||||
};
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
<!--#include virtual="/title.html" -->
|
||||
<script><!--#include virtual="/config.js" --></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js"></script>
|
||||
<script src="libs/app.bundle.min.js"></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react"></div>
|
||||
|
||||
Reference in New Issue
Block a user