mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-12 17:28:39 +00:00
Compare commits
14 Commits
6863
...
token-veri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a49b6140e0 | ||
|
|
c30d1e7479 | ||
|
|
cbbe58a1ec | ||
|
|
aef5328aeb | ||
|
|
f5ac1b6271 | ||
|
|
ca9f0a6788 | ||
|
|
e7c5ae5936 | ||
|
|
c43a319576 | ||
|
|
585c9aa0d2 | ||
|
|
768f10d966 | ||
|
|
704740969b | ||
|
|
d444a45f00 | ||
|
|
9fbbe05d6c | ||
|
|
3445c513ba |
@@ -47,6 +47,7 @@ import {
|
||||
dataChannelClosed,
|
||||
dataChannelOpened,
|
||||
e2eRttChanged,
|
||||
generateVisitorConfig,
|
||||
getConferenceOptions,
|
||||
kickedOut,
|
||||
lockStateChanged,
|
||||
@@ -277,7 +278,8 @@ class ConferenceConnector {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(resolve, reject) {
|
||||
constructor(resolve, reject, conference) {
|
||||
this._conference = conference;
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
this.reconnectTimeout = null;
|
||||
@@ -336,6 +338,26 @@ class ConferenceConnector {
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
generateVisitorConfig(APP.store.getState(), params);
|
||||
|
||||
connection.disconnect().then(() => {
|
||||
connect(this._conference.roomName).then(con => {
|
||||
const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
|
||||
|
||||
const jitsiTracks = localTracks.map(t => t.jitsiTrack);
|
||||
|
||||
// visitors connect muted
|
||||
jitsiTracks.forEach(t => t.mute());
|
||||
|
||||
// TODO disable option to unmute audio or video
|
||||
this._conference.startConference(con, jitsiTracks);
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.GRACEFUL_SHUTDOWN:
|
||||
APP.UI.notifyGracefulShutdown();
|
||||
break;
|
||||
@@ -732,7 +754,7 @@ export default {
|
||||
// XXX The API will take care of disconnecting from the XMPP
|
||||
// server (and, thus, leaving the room) on unload.
|
||||
return new Promise((resolve, reject) => {
|
||||
new ConferenceConnector(resolve, reject).connect();
|
||||
new ConferenceConnector(resolve, reject, this).connect();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1349,7 +1371,7 @@ export default {
|
||||
this._createRoom(localTracks);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new ConferenceConnector(resolve, reject).connect();
|
||||
new ConferenceConnector(resolve, reject, this).connect();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
12
config.js
12
config.js
@@ -580,9 +580,19 @@ var config = {
|
||||
// Require users to always specify a display name.
|
||||
// requireDisplayName: true,
|
||||
|
||||
// DEPRECATED! Use 'welcomePage.disabled' instead.
|
||||
// Whether to use a welcome page or not. In case it's false a random room
|
||||
// will be joined when no room is specified.
|
||||
enableWelcomePage: true,
|
||||
// enableWelcomePage: true,
|
||||
|
||||
// Configs for welcome page.
|
||||
// welcomePage: {
|
||||
// // Whether to disable welcome page. In case it's disabled a random room
|
||||
// // will be joined when no room is specified.
|
||||
// disabled: false,
|
||||
// // If set,landing page will redirect to this URL.
|
||||
// customUrl: ''
|
||||
// },
|
||||
|
||||
// Disable app shortcuts that are registered upon joining a conference
|
||||
// disableShortcuts: false,
|
||||
|
||||
@@ -2,7 +2,8 @@ $sidePanelWidth: 300px;
|
||||
|
||||
.prejoin-third-party {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
z-index: auto;
|
||||
|
||||
.content {
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -15,6 +15,17 @@ upstream jvb1 {
|
||||
server 127.0.0.1:9090;
|
||||
keepalive 2;
|
||||
}
|
||||
map $arg_vnode $prosody_node {
|
||||
default prosody;
|
||||
v1 v1;
|
||||
v2 v2;
|
||||
v3 v3;
|
||||
v4 v4;
|
||||
v5 v5;
|
||||
v6 v6;
|
||||
v7 v7;
|
||||
v8 v8;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
@@ -95,7 +106,7 @@ server {
|
||||
|
||||
# BOSH
|
||||
location = /http-bind {
|
||||
proxy_pass http://prosody/http-bind?prefix=$prefix&$args;
|
||||
proxy_pass http://$prosody_node/http-bind?prefix=$prefix&$args;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
@@ -104,7 +115,7 @@ server {
|
||||
|
||||
# xmpp websockets
|
||||
location = /xmpp-websocket {
|
||||
proxy_pass http://prosody/xmpp-websocket?prefix=$prefix&$args;
|
||||
proxy_pass http://$prosody_node/xmpp-websocket?prefix=$prefix&$args;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
1
globals.d.ts
vendored
1
globals.d.ts
vendored
@@ -24,6 +24,7 @@ declare global {
|
||||
JITSI_MEET_LITE_SDK?: boolean;
|
||||
interfaceConfig?: any;
|
||||
JitsiMeetJS?: any;
|
||||
JitsiMeetElectron?: any;
|
||||
}
|
||||
|
||||
const config: IConfig;
|
||||
|
||||
@@ -385,7 +385,7 @@ PODS:
|
||||
- react-native-video/Video (6.0.0-alpha.1):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (1.106.1):
|
||||
- react-native-webrtc (106.0.0):
|
||||
- JitsiWebRTC (~> 106.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
@@ -755,7 +755,7 @@ SPEC CHECKSUMS:
|
||||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-webrtc: 4a4c31be61f88d1d3356526eebce72f462a6760e
|
||||
react-native-webrtc: 0a407105bf428c9157f2e8d4d6f7c844dc185933
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
React-perflogger: 0458a87ea9a7342079e7a31b0d32b3734fb8415f
|
||||
React-RCTActionSheet: 22538001ea2926dea001111dd2846c13a0730bc9
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -74,7 +74,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -114,7 +114,7 @@
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "1.106.1",
|
||||
"react-native-webrtc": "106.0.0",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
@@ -13497,8 +13497,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-LH24V3aCAyNxrXkYsr4Syz9G+hDyfGo7JUu3suEbh1bS5Y4w8mwzTBHyGazLnD7/NDG5F783DY19/yCzR7stSQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -16501,13 +16501,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-webrtc": {
|
||||
"version": "1.106.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.106.1.tgz",
|
||||
"integrity": "sha512-955gqWFdISARz9D4hmnPzKQwpaU+AGqUbU+vBjzLCozUseSJ69tTQg2cShyPCBH6A1rwJQE+mrdjcpkeGbx3pQ==",
|
||||
"version": "106.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.0.tgz",
|
||||
"integrity": "sha512-nFl8WSNGMNxuIiaNAiJvILRcEC65yRxPOWTexLrM+vo44syt/4chEvzN9eOqXiPsOmsECQmwZupCUGR5XABUNg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"adm-zip": "0.5.9",
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
"event-target-shim": "6.0.2",
|
||||
"tar": "6.1.11"
|
||||
},
|
||||
@@ -30495,8 +30496,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-cxzr8vnJ6RyqWYzJ4LO09PqblJ6nIrJFFmzW8kPQgC1Nhq7sDAD896827q/shd+FE6bSoK0xVjDP+gW+JInS9A==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-LH24V3aCAyNxrXkYsr4Syz9G+hDyfGo7JUu3suEbh1bS5Y4w8mwzTBHyGazLnD7/NDG5F783DY19/yCzR7stSQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -32789,12 +32790,13 @@
|
||||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.106.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.106.1.tgz",
|
||||
"integrity": "sha512-955gqWFdISARz9D4hmnPzKQwpaU+AGqUbU+vBjzLCozUseSJ69tTQg2cShyPCBH6A1rwJQE+mrdjcpkeGbx3pQ==",
|
||||
"version": "106.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.0.tgz",
|
||||
"integrity": "sha512-nFl8WSNGMNxuIiaNAiJvILRcEC65yRxPOWTexLrM+vo44syt/4chEvzN9eOqXiPsOmsECQmwZupCUGR5XABUNg==",
|
||||
"requires": {
|
||||
"adm-zip": "0.5.9",
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
"event-target-shim": "6.0.2",
|
||||
"tar": "6.1.11"
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1549.0.0+877c4546/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -119,7 +119,7 @@
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "1.106.1",
|
||||
"react-native-webrtc": "106.0.0",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { clearNotifications, showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { setFatalError } from '../overlay/actions';
|
||||
import { isWelcomePageEnabled } from '../welcome/functions';
|
||||
|
||||
import {
|
||||
redirectToStaticPage,
|
||||
@@ -203,7 +204,7 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea
|
||||
|
||||
// if Welcome page is enabled redirect to welcome page after 3 sec, if
|
||||
// there is a thank you message to be shown, 0.5s otherwise.
|
||||
if (getState()['features/base/config'].enableWelcomePage) {
|
||||
if (isWelcomePageEnabled(getState())) {
|
||||
setTimeout(
|
||||
() => {
|
||||
dispatch(redirectWithStoredParams('/'));
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Conference } from '../conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking';
|
||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
||||
import { BlankPage, WelcomePage } from '../welcome';
|
||||
import { isWelcomePageEnabled } from '../welcome/functions';
|
||||
import { getCustomLandingPageURL, isWelcomePageEnabled } from '../welcome/functions';
|
||||
|
||||
/**
|
||||
* Determines which route is to be rendered in order to depict a specific Redux
|
||||
@@ -75,7 +75,13 @@ function _getWebWelcomePageRoute(state) {
|
||||
|
||||
if (isWelcomePageEnabled(state)) {
|
||||
if (isSupportedBrowser()) {
|
||||
route.component = WelcomePage;
|
||||
const customLandingPage = getCustomLandingPageURL(state);
|
||||
|
||||
if (customLandingPage) {
|
||||
route.href = customLandingPage;
|
||||
} else {
|
||||
route.component = WelcomePage;
|
||||
}
|
||||
} else {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
}
|
||||
|
||||
@@ -252,6 +252,34 @@ export function getConferenceOptions(stateful: IStateful) {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object aggregating the conference options.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store state.
|
||||
* @param {Array<string>} params - The received parameters.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function generateVisitorConfig(stateful: IStateful, params: Array<string>) {
|
||||
const [ vnode, focusJid ] = params;
|
||||
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
if (!config || !config.hosts) {
|
||||
logger.warn('Wrong configuration, missing hosts.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const oldDomain = config.hosts.domain;
|
||||
|
||||
config.hosts.domain = `${vnode}.meet.jitsi`;
|
||||
config.hosts.muc = config.hosts.muc.replace(oldDomain, config.hosts.domain);
|
||||
config.hosts.visitorFocus = focusJid;
|
||||
|
||||
config.bosh += `?vnode=${vnode}`;
|
||||
config.websocket += `?vnode=${vnode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTC timestamp when the first participant joined the conference.
|
||||
*
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface IJitsiConference {
|
||||
avModerationApprove: Function;
|
||||
avModerationReject: Function;
|
||||
createVideoSIPGWSession: Function;
|
||||
dial: Function;
|
||||
disableAVModeration: Function;
|
||||
enableAVModeration: Function;
|
||||
end: Function;
|
||||
@@ -74,6 +75,7 @@ export interface IJitsiConference {
|
||||
myUserId: Function;
|
||||
off: Function;
|
||||
on: Function;
|
||||
options: any;
|
||||
removeTrack: Function;
|
||||
replaceTrack: Function;
|
||||
room: IJitsiConferenceRoom;
|
||||
|
||||
@@ -127,6 +127,7 @@ export interface IConfig {
|
||||
preventExecution: boolean;
|
||||
}>;
|
||||
callDisplayName?: string;
|
||||
callFlowsEnabled?: boolean;
|
||||
callStatsConfigParams?: {
|
||||
additionalIDs?: {
|
||||
customerID?: string;
|
||||
@@ -339,10 +340,13 @@ export interface IConfig {
|
||||
domain: string;
|
||||
focus?: string;
|
||||
muc: string;
|
||||
visitorFocus: string;
|
||||
};
|
||||
iAmRecorder?: boolean;
|
||||
iAmSipGateway?: boolean;
|
||||
inviteAppName?: string | null;
|
||||
inviteServiceCallFlowsUrl?: string;
|
||||
inviteServiceUrl?: string;
|
||||
jaasActuatorUrl?: string;
|
||||
jaasFeedbackMetadataURL?: string;
|
||||
jaasTokenUrl?: string;
|
||||
@@ -504,6 +508,10 @@ export interface IConfig {
|
||||
webrtcIceUdpDisable?: boolean;
|
||||
websocket?: string;
|
||||
websocketKeepAliveUrl?: string;
|
||||
welcomePage?: {
|
||||
customUrl?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
whiteboard?: {
|
||||
collabServerBaseUrl?: string;
|
||||
enabled?: boolean;
|
||||
|
||||
@@ -60,7 +60,7 @@ export function getMeetingRegion(state: IReduxState) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getMultipleVideoSendingSupportFeatureFlag(state: IReduxState) {
|
||||
return navigator.product !== 'ReactNative' && isUnifiedPlanEnabled(state);
|
||||
return isUnifiedPlanEnabled(state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -338,6 +338,13 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
newValue.welcomePage = oldValue.welcomePage || {};
|
||||
if (oldValue.hasOwnProperty('enableWelcomePage')
|
||||
&& !newValue.welcomePage.hasOwnProperty('disabled')
|
||||
) {
|
||||
newValue.welcomePage.disabled = !oldValue.enableWelcomePage;
|
||||
}
|
||||
|
||||
newValue.prejoinConfig = oldValue.prejoinConfig || {};
|
||||
if (oldValue.hasOwnProperty('prejoinPageEnabled')
|
||||
&& !newValue.prejoinConfig.hasOwnProperty('enabled')
|
||||
|
||||
@@ -43,7 +43,8 @@ export const VIDEO_MUTISM_AUTHORITY = {
|
||||
AUDIO_ONLY: 1 << 0,
|
||||
BACKGROUND: 1 << 1,
|
||||
USER: 1 << 2,
|
||||
CAR_MODE: 1 << 3
|
||||
CAR_MODE: 1 << 3,
|
||||
SCREEN_SHARE: 1 << 4
|
||||
};
|
||||
|
||||
/* eslint-enable no-bitwise */
|
||||
|
||||
@@ -4,9 +4,17 @@ import { IReduxState, IStore } from '../../app/types';
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setAudioOnly } from '../audio-only/actions';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import {
|
||||
setScreenshareMuted,
|
||||
setVideoMuted
|
||||
} from '../media/actions';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from '../media/constants';
|
||||
|
||||
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from './actions.any';
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from './functions';
|
||||
import { addLocalTrack, replaceLocalTrack } from './actions.any';
|
||||
import { getLocalDesktopTrack, getTrackState, isLocalVideoTrackDesktop } from './functions.native';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -31,7 +39,8 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
|
||||
_startScreenSharing(dispatch, state);
|
||||
}
|
||||
} else {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
dispatch(setScreenshareMuted(true));
|
||||
dispatch(setVideoMuted(false, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
};
|
||||
@@ -47,26 +56,33 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startScreenSharing(dispatch: Function, state: IReduxState) {
|
||||
async function _startScreenSharing(dispatch: Function, state: IReduxState) {
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||
.then((tracks: any[]) => {
|
||||
try {
|
||||
const tracks: any[] = await JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] });
|
||||
const track = tracks[0];
|
||||
const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const currentJitsiTrack = currentLocalTrack?.jitsiTrack;
|
||||
const currentLocalDesktopTrack = getLocalDesktopTrack(getTrackState(state));
|
||||
const currentJitsiTrack = currentLocalDesktopTrack?.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||
// The first time the user shares the screen we add the track and create the transceiver.
|
||||
// Afterwards, we just replace the old track, so the transceiver will be reused.
|
||||
if (currentJitsiTrack) {
|
||||
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||
} else {
|
||||
dispatch(addLocalTrack(track));
|
||||
}
|
||||
|
||||
dispatch(setVideoMuted(true, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
|
||||
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
} catch (error: any) {
|
||||
console.log('ERROR creating ScreeSharing stream ', error);
|
||||
|
||||
setPictureInPictureEnabled(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +313,9 @@ export function isLocalTrackMuted(tracks: ITrack[], mediaType: MediaType) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalVideoTrackDesktop(state: IReduxState) {
|
||||
const videoTrack = getLocalVideoTrack(getTrackState(state));
|
||||
const desktopTrack = getLocalDesktopTrack(getTrackState(state));
|
||||
|
||||
return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||
return desktopTrack !== undefined && !desktopTrack.muted;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/chrome-banner');
|
||||
@@ -1,5 +1,6 @@
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
|
||||
// @ts-ignore
|
||||
import { DesktopPicker } from './components';
|
||||
|
||||
/**
|
||||
@@ -10,7 +11,7 @@ import { DesktopPicker } from './components';
|
||||
* a DesktopCapturerSource has been chosen.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showDesktopPicker(options = {}, onSourceChoose) {
|
||||
export function showDesktopPicker(options: { desktopSharingSources?: any; } = {}, onSourceChoose: Function) {
|
||||
const { desktopSharingSources } = options;
|
||||
|
||||
return openDialog(DesktopPicker, {
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -11,8 +10,8 @@ import logger from './logger';
|
||||
* return native image object used for the preview image of the source.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function obtainDesktopSources(types, options = {}) {
|
||||
const capturerOptions = {
|
||||
export function obtainDesktopSources(types: string[], options: { thumbnailSize?: Object; } = {}) {
|
||||
const capturerOptions: any = {
|
||||
types
|
||||
};
|
||||
|
||||
@@ -23,10 +22,10 @@ export function obtainDesktopSources(types, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { JitsiMeetElectron } = window;
|
||||
|
||||
if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) {
|
||||
if (JitsiMeetElectron?.obtainDesktopStreams) {
|
||||
JitsiMeetElectron.obtainDesktopStreams(
|
||||
sources => resolve(_separateSourcesByType(sources)),
|
||||
error => {
|
||||
(sources: Array<{ id: string; }>) => resolve(_separateSourcesByType(sources)),
|
||||
(error: Error) => {
|
||||
logger.error(
|
||||
`Error while obtaining desktop sources: ${error}`);
|
||||
reject(error);
|
||||
@@ -54,8 +53,8 @@ export function obtainDesktopSources(types, options = {}) {
|
||||
* @returns {Object} An object with the sources split into separate arrays based
|
||||
* on source type.
|
||||
*/
|
||||
function _separateSourcesByType(sources = []) {
|
||||
const sourcesByType = {
|
||||
function _separateSourcesByType(sources: Array<{ id: string; }> = []) {
|
||||
const sourcesByType: any = {
|
||||
screen: [],
|
||||
window: []
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/desktop-picker');
|
||||
@@ -32,10 +32,13 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
inviteDomain
|
||||
};
|
||||
|
||||
// TODO: implement support for gradients.
|
||||
action.value.avatarBackgrounds = avatarBackgrounds.filter(
|
||||
(color: string) => !color.includes('linear-gradient')
|
||||
);
|
||||
// The backend may send an empty string, make sure we skip that.
|
||||
if (Array.isArray(avatarBackgrounds)) {
|
||||
// TODO: implement support for gradients.
|
||||
action.value.avatarBackgrounds = avatarBackgrounds.filter(
|
||||
(color: string) => !color.includes('linear-gradient')
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Utility class with no dependencies. Used in components that are stripped in separate bundles
|
||||
* and requires as less dependencies as possible.
|
||||
*/
|
||||
|
||||
import { getURLWithoutParams } from '../base/connection/utils';
|
||||
import { doGetJSON } from '../base/util';
|
||||
import { doGetJSON } from '../base/util/httpUtils';
|
||||
|
||||
/**
|
||||
* Formats the conference pin in readable way for UI to display it.
|
||||
@@ -49,7 +47,7 @@ export function getDialInConferenceID(
|
||||
roomName: string,
|
||||
mucURL: string,
|
||||
url: URL
|
||||
): Promise<Object> {
|
||||
): Promise<any> {
|
||||
const separator = baseUrl.includes('?') ? '&' : '?';
|
||||
const conferenceIDURL
|
||||
= `${baseUrl}${separator}conference=${roomName}@${mucURL}&url=${getURLWithoutParams(url).href}`;
|
||||
@@ -75,7 +73,7 @@ export function getDialInNumbers(
|
||||
url: string,
|
||||
roomName: string,
|
||||
mucURL: string
|
||||
): Promise<*> {
|
||||
): Promise<any> {
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
|
||||
// when roomName and mucURL are available
|
||||
@@ -1,10 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { getLocalParticipant, getParticipantCount } from '../base/participants';
|
||||
import { inviteVideoRooms } from '../videosipgw';
|
||||
import { IStore } from '../app/types';
|
||||
import { getInviteURL } from '../base/connection/functions';
|
||||
import { getLocalParticipant, getParticipantCount } from '../base/participants/functions';
|
||||
import { inviteVideoRooms } from '../videosipgw/actions';
|
||||
|
||||
import { getDialInConferenceID, getDialInNumbers } from './_utils';
|
||||
import {
|
||||
@@ -22,6 +19,7 @@ import {
|
||||
inviteSipEndpoints
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IInvitee } from './types';
|
||||
|
||||
/**
|
||||
* Creates a (redux) action to signal that a click/tap has been performed on
|
||||
@@ -64,11 +62,11 @@ export function hideAddPeopleDialog() {
|
||||
* of invitees who were not invited (i.e. Invites were not sent to them).
|
||||
*/
|
||||
export function invite(
|
||||
invitees: Array<Object>,
|
||||
showCalleeInfo: boolean = false) {
|
||||
invitees: IInvitee[],
|
||||
showCalleeInfo = false) {
|
||||
return (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: Function): Promise<Array<Object>> => {
|
||||
dispatch: IStore['dispatch'],
|
||||
getState: IStore['getState']): Promise<Array<Object>> => {
|
||||
const state = getState();
|
||||
const participantsCount = getParticipantCount(state);
|
||||
const { calleeInfoVisible } = state['features/invite'];
|
||||
@@ -89,12 +87,12 @@ export function invite(
|
||||
return new Promise(resolve => {
|
||||
dispatch(addPendingInviteRequest({
|
||||
invitees,
|
||||
callback: failedInvitees => resolve(failedInvitees)
|
||||
callback: (failedInvitees: any) => resolve(failedInvitees)
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
let allInvitePromises = [];
|
||||
let allInvitePromises: Promise<any>[] = [];
|
||||
let invitesLeftToSend = [ ...invitees ];
|
||||
|
||||
const {
|
||||
@@ -105,8 +103,8 @@ export function invite(
|
||||
const inviteUrl = getInviteURL(state);
|
||||
const { sipInviteUrl } = state['features/base/config'];
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { name: displayName } = getLocalParticipant(state);
|
||||
const { jwt = '' } = state['features/base/jwt'];
|
||||
const { name: displayName } = getLocalParticipant(state) ?? {};
|
||||
|
||||
// First create all promises for dialing out.
|
||||
const phoneNumbers
|
||||
@@ -123,7 +121,7 @@ export function invite(
|
||||
= invitesLeftToSend.filter(
|
||||
invitee => invitee !== item);
|
||||
})
|
||||
.catch(error =>
|
||||
.catch((error: Error) =>
|
||||
logger.error('Error inviting phone number:', error));
|
||||
});
|
||||
|
||||
@@ -138,8 +136,8 @@ export function invite(
|
||||
// filter all rooms and users from {@link invitesLeftToSend}.
|
||||
const peopleInvitePromise
|
||||
= invitePeopleAndChatRooms(
|
||||
callFlowsEnabled
|
||||
? inviteServiceCallFlowsUrl : inviteServiceUrl,
|
||||
(callFlowsEnabled
|
||||
? inviteServiceCallFlowsUrl : inviteServiceUrl) ?? '',
|
||||
inviteUrl,
|
||||
jwt,
|
||||
usersAndRooms)
|
||||
@@ -173,6 +171,8 @@ export function invite(
|
||||
|
||||
conference && inviteSipEndpoints(
|
||||
sipEndpoints,
|
||||
|
||||
// @ts-ignore
|
||||
locationURL,
|
||||
sipInviteUrl,
|
||||
jwt,
|
||||
@@ -196,12 +196,12 @@ export function invite(
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateDialInNumbers() {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const { dialInConfCodeUrl, dialInNumbersUrl, hosts }
|
||||
= state['features/base/config'];
|
||||
const { numbersFetched } = state['features/invite'];
|
||||
const mucURL = hosts && hosts.muc;
|
||||
const mucURL = hosts?.muc;
|
||||
|
||||
if (numbersFetched || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
|
||||
// URLs for fetching dial in numbers not defined
|
||||
@@ -209,10 +209,10 @@ export function updateDialInNumbers() {
|
||||
}
|
||||
|
||||
const { locationURL = {} } = state['features/base/connection'];
|
||||
const { room } = state['features/base/conference'];
|
||||
const { room = '' } = state['features/base/conference'];
|
||||
|
||||
Promise.all([
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL),
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
|
||||
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
|
||||
])
|
||||
.then(([ dialInNumbers, { conference, id, message, sipUri } ]) => {
|
||||
@@ -251,7 +251,7 @@ export function updateDialInNumbers() {
|
||||
*/
|
||||
export function setCalleeInfoVisible(
|
||||
calleeInfoVisible: boolean,
|
||||
initialCalleeInfo: ?Object) {
|
||||
initialCalleeInfo?: Object) {
|
||||
return {
|
||||
type: SET_CALLEE_INFO_VISIBLE,
|
||||
calleeInfoVisible,
|
||||
@@ -269,7 +269,7 @@ export function setCalleeInfoVisible(
|
||||
* }}
|
||||
*/
|
||||
export function addPendingInviteRequest(
|
||||
request: { invitees: Array<Object>, callback: Function }) {
|
||||
request: { callback: Function; invitees: Array<Object>; }) {
|
||||
return {
|
||||
type: ADD_PENDING_INVITE_REQUEST,
|
||||
request
|
||||
@@ -1,11 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ADD_PEOPLE_ENABLED, getFeatureFlag } from '../base/flags';
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { IStore } from '../app/types';
|
||||
import { ADD_PEOPLE_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
// @ts-ignore
|
||||
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
import { beginShareRoom } from '../share-room';
|
||||
import { beginShareRoom } from '../share-room/actions';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from './functions';
|
||||
|
||||
@@ -18,7 +20,7 @@ export * from './actions.any';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function doInvitePeople() {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const addPeopleEnabled = getFeatureFlag(state, ADD_PEOPLE_ENABLED, true)
|
||||
&& (isAddPeopleEnabled(state) || isDialOutEnabled(state));
|
||||
@@ -205,7 +205,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
||||
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||
const dialOutEnabled = isDialOutEnabled(state);
|
||||
const hideInviteContacts = iAmRecorder || (!addPeopleEnabled && !dialOutEnabled);
|
||||
const dialIn = state['features/invite'];
|
||||
const dialIn = state['features/invite']; // @ts-ignore
|
||||
const phoneNumber = dialIn?.numbers ? _getDefaultPhoneNumber(dialIn.numbers) : undefined;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import { getActiveSession } from '../../features/recording/functions';
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getRoomName } from '../base/conference/functions';
|
||||
import { getInviteURL } from '../base/connection/functions';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { i18next } from '../base/i18n';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import {
|
||||
appendURLParam,
|
||||
parseURIString,
|
||||
parseURLParams
|
||||
} from '../base/util';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import { appendURLParam, parseURIString } from '../base/util/uri';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { getActiveSession } from '../recording/functions';
|
||||
|
||||
import { getDialInConferenceID, getDialInNumbers } from './_utils';
|
||||
import {
|
||||
@@ -24,8 +21,7 @@ import {
|
||||
} from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
declare var $: Function;
|
||||
declare var interfaceConfig: Object;
|
||||
declare let $: any;
|
||||
|
||||
export const sharingFeatures = {
|
||||
email: 'email',
|
||||
@@ -44,7 +40,7 @@ export const sharingFeatures = {
|
||||
export function checkDialNumber(
|
||||
dialNumber: string,
|
||||
dialOutAuthUrl: string
|
||||
): Promise<Object> {
|
||||
): Promise<{ allow?: boolean; country?: string; phone?: string; }> {
|
||||
const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -61,7 +57,7 @@ export function checkDialNumber(
|
||||
* numbers.
|
||||
* @returns {string} A string with only numbers.
|
||||
*/
|
||||
export function getDigitsOnly(text: string = ''): string {
|
||||
export function getDigitsOnly(text = ''): string {
|
||||
return text.replace(/\D/g, '');
|
||||
}
|
||||
|
||||
@@ -70,41 +66,41 @@ export function getDigitsOnly(text: string = ''): string {
|
||||
*/
|
||||
export type GetInviteResultsOptions = {
|
||||
|
||||
/**
|
||||
* The endpoint to use for checking phone number validity.
|
||||
*/
|
||||
dialOutAuthUrl: string,
|
||||
|
||||
/**
|
||||
* Whether or not to search for people.
|
||||
*/
|
||||
addPeopleEnabled: boolean,
|
||||
addPeopleEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The endpoint to use for checking phone number validity.
|
||||
*/
|
||||
dialOutAuthUrl: string;
|
||||
|
||||
/**
|
||||
* Whether or not to check phone numbers.
|
||||
*/
|
||||
dialOutEnabled: boolean,
|
||||
dialOutEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The jwt token to pass to the search service.
|
||||
*/
|
||||
jwt: string;
|
||||
|
||||
/**
|
||||
* Array with the query types that will be executed -
|
||||
* "conferenceRooms" | "user" | "room".
|
||||
*/
|
||||
peopleSearchQueryTypes: Array<string>,
|
||||
peopleSearchQueryTypes: Array<string>;
|
||||
|
||||
/**
|
||||
* The url to query for people.
|
||||
*/
|
||||
peopleSearchUrl: string,
|
||||
peopleSearchUrl: string;
|
||||
|
||||
/**
|
||||
* Whether or not to check sip invites.
|
||||
*/
|
||||
sipInviteEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The jwt token to pass to the search service.
|
||||
*/
|
||||
jwt: string
|
||||
sipInviteEnabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -118,8 +114,7 @@ export type GetInviteResultsOptions = {
|
||||
export function getInviteResultsForQuery(
|
||||
query: string,
|
||||
options: GetInviteResultsOptions
|
||||
): Promise<*> {
|
||||
|
||||
): Promise<any> {
|
||||
const text = query.trim();
|
||||
|
||||
const {
|
||||
@@ -183,12 +178,12 @@ export function getInviteResultsForQuery(
|
||||
phone: text
|
||||
});
|
||||
} else {
|
||||
phoneNumberPromise = Promise.resolve({});
|
||||
phoneNumberPromise = Promise.resolve<{ allow?: boolean; country?: string; phone?: string; }>({});
|
||||
}
|
||||
|
||||
return Promise.all([ peopleSearchPromise, phoneNumberPromise ])
|
||||
.then(([ peopleResults, phoneResults ]) => {
|
||||
const results = [
|
||||
const results: any[] = [
|
||||
...peopleResults
|
||||
];
|
||||
|
||||
@@ -233,7 +228,7 @@ export function getInviteTextiOS({
|
||||
state,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Object) {
|
||||
}: { phoneNumber?: string | null; state: IReduxState; t?: Function; }) {
|
||||
if (!isIosMobileBrowser()) {
|
||||
return '';
|
||||
}
|
||||
@@ -246,23 +241,23 @@ export function getInviteTextiOS({
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteTextiOSPersonal', { name: localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
? t?.('info.inviteTextiOSPersonal', { name: localParticipantName })
|
||||
: t?.('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += ' ';
|
||||
|
||||
invite += t('info.inviteTextiOSInviteUrl', { inviteUrl });
|
||||
invite += t?.('info.inviteTextiOSInviteUrl', { inviteUrl });
|
||||
invite += ' ';
|
||||
|
||||
if (shouldDisplayDialIn(dialIn) && isSharingEnabled(sharingFeatures.dialIn)) {
|
||||
invite += t('info.inviteTextiOSPhone', {
|
||||
invite += t?.('info.inviteTextiOSPhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID,
|
||||
didUrl: getDialInfoPageURL(state)
|
||||
});
|
||||
}
|
||||
invite += ' ';
|
||||
invite += t('info.inviteTextiOSJoinSilent', { silentUrl: `${inviteURL}#config.startSilent=true` });
|
||||
invite += t?.('info.inviteTextiOSJoinSilent', { silentUrl: `${inviteURL}#config.startSilent=true` });
|
||||
|
||||
return invite;
|
||||
}
|
||||
@@ -276,27 +271,25 @@ export function getInviteText({
|
||||
state,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Object) {
|
||||
}: { phoneNumber?: string | null; state: IReduxState; t?: Function; }) {
|
||||
const dialIn = state['features/invite'];
|
||||
const inviteUrl = getInviteURL(state);
|
||||
const currentLiveStreamingSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const liveStreamViewURL
|
||||
= currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL;
|
||||
const liveStreamViewURL = currentLiveStreamingSession?.liveStreamViewURL;
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localParticipantName = localParticipant?.name;
|
||||
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
? t?.('info.inviteURLFirstPartPersonal', { name: localParticipantName })
|
||||
: t?.('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += t('info.inviteURLSecondPart', {
|
||||
invite += t?.('info.inviteURLSecondPart', {
|
||||
url: inviteURL
|
||||
});
|
||||
|
||||
if (liveStreamViewURL) {
|
||||
const liveStream = t('info.inviteLiveStream', {
|
||||
const liveStream = t?.('info.inviteLiveStream', {
|
||||
url: liveStreamViewURL
|
||||
});
|
||||
|
||||
@@ -304,11 +297,11 @@ export function getInviteText({
|
||||
}
|
||||
|
||||
if (shouldDisplayDialIn(dialIn) && isSharingEnabled(sharingFeatures.dialIn)) {
|
||||
const dial = t('info.invitePhone', {
|
||||
const dial = t?.('info.invitePhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID
|
||||
});
|
||||
const moreNumbers = t('info.invitePhoneAlternatives', {
|
||||
const moreNumbers = t?.('info.invitePhoneAlternatives', {
|
||||
url: getDialInfoPageURL(state),
|
||||
silentUrl: `${inviteURL}#config.startSilent=true`
|
||||
});
|
||||
@@ -328,8 +321,8 @@ export function getInviteText({
|
||||
* @returns {Object} An object with keys as user types and values as the number
|
||||
* of invites for that type.
|
||||
*/
|
||||
export function getInviteTypeCounts(inviteItems: Array<Object> = []) {
|
||||
const inviteTypeCounts = {};
|
||||
export function getInviteTypeCounts(inviteItems: Array<{ type: string; }> = []) {
|
||||
const inviteTypeCounts: any = {};
|
||||
|
||||
inviteItems.forEach(({ type }) => {
|
||||
if (!inviteTypeCounts[type]) {
|
||||
@@ -352,51 +345,51 @@ export function getInviteTypeCounts(inviteItems: Array<Object> = []) {
|
||||
* items to invite.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
export function invitePeopleAndChatRooms(
|
||||
inviteServiceUrl: string,
|
||||
inviteUrl: string,
|
||||
jwt: string,
|
||||
inviteItems: Array<Object>
|
||||
): Promise<void> {
|
||||
): Promise<any> {
|
||||
|
||||
if (!inviteItems || inviteItems.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return fetch(
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if adding people is currently enabled.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @param {IReduxState} state - Current state.
|
||||
* @returns {boolean} Indication of whether adding people is currently enabled.
|
||||
*/
|
||||
export function isAddPeopleEnabled(state: Object): boolean {
|
||||
export function isAddPeopleEnabled(state: IReduxState): boolean {
|
||||
const { peopleSearchUrl } = state['features/base/config'];
|
||||
|
||||
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl) && !isVpaasMeeting(state);
|
||||
return Boolean(state['features/base/jwt'].jwt && Boolean(peopleSearchUrl) && !isVpaasMeeting(state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if dial out is currently enabled or not.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @param {IReduxState} state - Current state.
|
||||
* @returns {boolean} Indication of whether dial out is currently enabled.
|
||||
*/
|
||||
export function isDialOutEnabled(state: Object): boolean {
|
||||
export function isDialOutEnabled(state: IReduxState): boolean {
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
return isLocalParticipantModerator(state)
|
||||
@@ -406,10 +399,10 @@ export function isDialOutEnabled(state: Object): boolean {
|
||||
/**
|
||||
* Determines if inviting sip endpoints is enabled or not.
|
||||
*
|
||||
* @param {Object} state - Current state.
|
||||
* @param {IReduxState} state - Current state.
|
||||
* @returns {boolean} Indication of whether dial out is currently enabled.
|
||||
*/
|
||||
export function isSipInviteEnabled(state: Object): boolean {
|
||||
export function isSipInviteEnabled(state: IReduxState): boolean {
|
||||
const { sipInviteUrl } = state['features/base/config'];
|
||||
|
||||
return isJwtFeatureEnabled(state, 'sip-outbound-call') && Boolean(sipInviteUrl);
|
||||
@@ -473,7 +466,7 @@ export function searchDirectory( // eslint-disable-line max-params
|
||||
jwt: string,
|
||||
text: string,
|
||||
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
|
||||
): Promise<Array<Object>> {
|
||||
): Promise<Array<{ type: string; }>> {
|
||||
|
||||
const query = encodeURIComponent(text);
|
||||
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
|
||||
@@ -502,14 +495,13 @@ export function searchDirectory( // eslint-disable-line max-params
|
||||
* Returns descriptive text that can be used to invite participants to a meeting
|
||||
* (share via mobile or use it for calendar event description).
|
||||
*
|
||||
* @param {Object} state - The current state.
|
||||
* @param {IReduxState} state - The current state.
|
||||
* @param {string} inviteUrl - The conference/location URL.
|
||||
* @param {boolean} useHtml - Whether to return html text.
|
||||
* @returns {Promise<string>} A {@code Promise} resolving with a
|
||||
* descriptive text that can be used to invite participants to a meeting.
|
||||
*/
|
||||
export function getShareInfoText(
|
||||
state: Object, inviteUrl: string, useHtml: ?boolean): Promise<string> {
|
||||
export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?: boolean): Promise<string> {
|
||||
let roomUrl = _decodeRoomURI(inviteUrl);
|
||||
const includeDialInfo = state['features/base/config'] !== undefined;
|
||||
|
||||
@@ -534,7 +526,7 @@ export function getShareInfoText(
|
||||
const { dialInConfCodeUrl, dialInNumbersUrl, hosts }
|
||||
= state['features/base/config'];
|
||||
const { locationURL = {} } = state['features/base/connection'];
|
||||
const mucURL = hosts && hosts.muc;
|
||||
const mucURL = hosts?.muc;
|
||||
|
||||
if (!dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
|
||||
// URLs for fetching dial in numbers not defined
|
||||
@@ -542,7 +534,7 @@ export function getShareInfoText(
|
||||
}
|
||||
|
||||
numbersPromise = Promise.all([
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL),
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
|
||||
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
|
||||
]).then(([ numbers, {
|
||||
conference, id, message } ]) => {
|
||||
@@ -592,16 +584,16 @@ export function getShareInfoText(
|
||||
/**
|
||||
* Generates the URL for the static dial in info page.
|
||||
*
|
||||
* @param {Object} state - The state from the Redux store.
|
||||
* @param {IReduxState} state - The state from the Redux store.
|
||||
* @param {string?} roomName - The conference name. Optional name, if missing will be extracted from state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialInfoPageURL(state: Object, roomName: ?string) {
|
||||
export function getDialInfoPageURL(state: IReduxState, roomName?: string) {
|
||||
const { didPageUrl } = state['features/dynamic-branding'];
|
||||
const conferenceName = roomName ?? getRoomName(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { href } = locationURL;
|
||||
const room = _decodeRoomURI(conferenceName);
|
||||
const { href = '' } = locationURL ?? {};
|
||||
const room = _decodeRoomURI(conferenceName ?? '');
|
||||
|
||||
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/${DIAL_IN_INFO_PAGE_PATH_NAME}`;
|
||||
|
||||
@@ -615,7 +607,7 @@ export function getDialInfoPageURL(state: Object, roomName: ?string) {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialInfoPageURLForURIString(
|
||||
uri: ?string) {
|
||||
uri?: string) {
|
||||
if (!uri) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -637,7 +629,7 @@ export function getDialInfoPageURLForURIString(
|
||||
* @param {Object} dialIn - Dial in information.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldDisplayDialIn(dialIn: Object) {
|
||||
export function shouldDisplayDialIn(dialIn: any) {
|
||||
const { conferenceID, numbers, numbersEnabled } = dialIn;
|
||||
const phoneNumber = _getDefaultPhoneNumber(numbers);
|
||||
|
||||
@@ -656,7 +648,7 @@ export function shouldDisplayDialIn(dialIn: Object) {
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasMultipleNumbers(dialInNumbers: ?Object) {
|
||||
export function hasMultipleNumbers(dialInNumbers?: { numbers: Object; } | string[]) {
|
||||
if (!dialInNumbers) {
|
||||
return false;
|
||||
}
|
||||
@@ -682,7 +674,7 @@ export function hasMultipleNumbers(dialInNumbers: ?Object) {
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function _getDefaultPhoneNumber(
|
||||
dialInNumbers: ?Object): ?string {
|
||||
dialInNumbers?: { numbers: any; } | Array<{ default: string; formattedNumber: string; }>): string | null {
|
||||
|
||||
if (!dialInNumbers) {
|
||||
return null;
|
||||
@@ -743,22 +735,23 @@ export function _decodeRoomURI(url: string) {
|
||||
/**
|
||||
* Returns the stored conference id.
|
||||
*
|
||||
* @param {Object | Function} stateful - The Object or Function that can be
|
||||
* @param {IStateful} stateful - The Object or Function that can be
|
||||
* resolved to a Redux state object with the toState function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getConferenceId(stateful: Object | Function) {
|
||||
export function getConferenceId(stateful: IStateful) {
|
||||
return toState(stateful)['features/invite'].conferenceID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default dial in number from the store.
|
||||
*
|
||||
* @param {Object | Function} stateful - The Object or Function that can be
|
||||
* @param {IStateful} stateful - The Object or Function that can be
|
||||
* resolved to a Redux state object with the toState function.
|
||||
* @returns {string | null}
|
||||
*/
|
||||
export function getDefaultDialInNumber(stateful: Object | Function) {
|
||||
export function getDefaultDialInNumber(stateful: IStateful) {
|
||||
// @ts-ignore
|
||||
return _getDefaultPhoneNumber(toState(stateful)['features/invite'].numbers);
|
||||
}
|
||||
|
||||
@@ -831,14 +824,14 @@ export function isSharingEnabled(sharingFeature: string) {
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function inviteSipEndpoints( // eslint-disable-line max-params
|
||||
inviteItems: Array<Object>,
|
||||
inviteItems: Array<{ address: string; }>,
|
||||
locationURL: URL,
|
||||
sipInviteUrl: string,
|
||||
jwt: string,
|
||||
roomName: string,
|
||||
roomPassword: String,
|
||||
displayName: string
|
||||
): Promise<void> {
|
||||
): Promise<any> {
|
||||
if (inviteItems.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -851,26 +844,26 @@ export function inviteSipEndpoints( // eslint-disable-line max-params
|
||||
});
|
||||
|
||||
return fetch(
|
||||
sipInviteUrl,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
callParams: {
|
||||
callUrlInfo: {
|
||||
baseUrl,
|
||||
callName: roomName
|
||||
},
|
||||
passcode: roomPassword
|
||||
},
|
||||
sipClientParams: {
|
||||
displayName,
|
||||
sipAddress: inviteItems.map(item => item.address)
|
||||
}
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${jwt}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
sipInviteUrl,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
callParams: {
|
||||
callUrlInfo: {
|
||||
baseUrl,
|
||||
callName: roomName
|
||||
},
|
||||
passcode: roomPassword
|
||||
},
|
||||
sipClientParams: {
|
||||
displayName,
|
||||
sipAddress: inviteItems.map(item => item.address)
|
||||
}
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${jwt}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
// @flow
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
|
||||
import {
|
||||
CONFERENCE_JOINED
|
||||
} from '../base/conference';
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import {
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_JOINED_SOUND_ID,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED,
|
||||
PARTICIPANT_UPDATED
|
||||
} from '../base/participants/actionTypes';
|
||||
import { pinParticipant } from '../base/participants/actions';
|
||||
import { PARTICIPANT_JOINED_SOUND_ID } from '../base/participants/constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantCount,
|
||||
getParticipantPresenceStatus,
|
||||
getRemoteParticipants,
|
||||
pinParticipant
|
||||
} from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
getRemoteParticipants
|
||||
} from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import {
|
||||
playSound,
|
||||
registerSound,
|
||||
stopSound,
|
||||
unregisterSound
|
||||
} from '../base/sounds';
|
||||
} from '../base/sounds/actions';
|
||||
import {
|
||||
CALLING,
|
||||
CONNECTED_USER,
|
||||
@@ -29,7 +30,7 @@ import {
|
||||
INVITED,
|
||||
REJECTED,
|
||||
RINGING
|
||||
} from '../presence-status';
|
||||
} from '../presence-status/constants';
|
||||
|
||||
import {
|
||||
SET_CALLEE_INFO_VISIBLE,
|
||||
@@ -49,8 +50,6 @@ import {
|
||||
import logger from './logger';
|
||||
import { sounds } from './sounds';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Maps the presence status with the ID of the sound that will be played when
|
||||
* the status is received.
|
||||
@@ -84,7 +83,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
if (action.type === SET_CALLEE_INFO_VISIBLE) {
|
||||
if (action.calleeInfoVisible) {
|
||||
dispatch(pinParticipant(getLocalParticipant(state).id));
|
||||
dispatch(pinParticipant(getLocalParticipant(state)?.id));
|
||||
} else {
|
||||
// unpin participant
|
||||
dispatch(pinParticipant());
|
||||
@@ -124,10 +123,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
const oldSoundId
|
||||
= oldParticipantPresence
|
||||
&& statusToRingtone[oldParticipantPresence];
|
||||
&& statusToRingtone[oldParticipantPresence as keyof typeof statusToRingtone];
|
||||
const newSoundId
|
||||
= newParticipantPresence
|
||||
&& statusToRingtone[newParticipantPresence];
|
||||
&& statusToRingtone[newParticipantPresence as keyof typeof statusToRingtone];
|
||||
|
||||
|
||||
if (oldSoundId === newSoundId) {
|
||||
@@ -159,10 +158,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* (not poltergeist, shared video, etc.) participants in the call.
|
||||
*
|
||||
* @param {Object} action - The redux action.
|
||||
* @param {ReduxStore} store - The redux store.
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _maybeHideCalleeInfo(action, store) {
|
||||
function _maybeHideCalleeInfo(action: AnyAction, store: IStore) {
|
||||
const state = store.getState();
|
||||
|
||||
if (!state['features/invite'].calleeInfoVisible) {
|
||||
@@ -188,10 +187,10 @@ function _maybeHideCalleeInfo(action, store) {
|
||||
/**
|
||||
* Executes the pending invitation requests if any.
|
||||
*
|
||||
* @param {ReduxStore} store - The redux store.
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConferenceJoined(store) {
|
||||
function _onConferenceJoined(store: IStore) {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
const pendingInviteRequests
|
||||
@@ -1,10 +1,11 @@
|
||||
// @flow
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { hideDialog, openDialog } from '../base/dialog';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { IStore } from '../app/types';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { BEGIN_ADD_PEOPLE, HIDE_ADD_PEOPLE_DIALOG } from './actionTypes';
|
||||
import { AddPeopleDialog } from './components';
|
||||
import AddPeopleDialog from './components/add-people-dialog/web/AddPeopleDialog';
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _beginAddPeople({ dispatch }, next, action) {
|
||||
function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
dispatch(openDialog(AddPeopleDialog));
|
||||
@@ -58,7 +59,7 @@ function _beginAddPeople({ dispatch }, next, action) {
|
||||
* @private
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _hideAddPeopleDialog({ dispatch }, next, action) {
|
||||
function _hideAddPeopleDialog({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(hideDialog(AddPeopleDialog));
|
||||
|
||||
return next(action);
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||
} from './actionTypes';
|
||||
import logger from './logger';
|
||||
import { IInvitee } from './types';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
@@ -27,12 +28,12 @@ export interface IInviteState {
|
||||
conferenceID?: string | number;
|
||||
error?: Error;
|
||||
initialCalleeInfo?: Object;
|
||||
numbers?: string;
|
||||
numbers?: string[];
|
||||
numbersEnabled: boolean;
|
||||
numbersFetched: boolean;
|
||||
pendingInviteRequests: Array<{
|
||||
callback: Function;
|
||||
invitees: Array<Object>;
|
||||
invitees: IInvitee[];
|
||||
}>;
|
||||
sipUri?: string;
|
||||
}
|
||||
|
||||
5
react/features/invite/types.ts
Normal file
5
react/features/invite/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface IInvitee {
|
||||
address: string;
|
||||
number: string;
|
||||
type: string;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ function pollForStatus(
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await executeDialOutStatusRequest(getDialOutStatusUrl(state), reqId);
|
||||
const res = await executeDialOutStatusRequest(getDialOutStatusUrl(state) ?? '', reqId);
|
||||
|
||||
switch (res) {
|
||||
case DIAL_OUT_STATUS.INITIATED:
|
||||
@@ -153,7 +153,7 @@ export function dialOut(onSuccess: Function, onFail: Function) {
|
||||
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
const state = getState();
|
||||
const reqId = uuidv4();
|
||||
const url = getDialOutUrl(state);
|
||||
const url = getDialOutUrl(state) ?? '';
|
||||
const conferenceUrl = getDialOutConferenceUrl(state);
|
||||
const phoneNumber = getFullDialOutNumber(state);
|
||||
const countryCode = getDialOutCountry(state).code.toUpperCase();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
DELETE_RECENT_LIST_ENTRY,
|
||||
_STORE_CURRENT_CONFERENCE,
|
||||
@@ -1,9 +1,13 @@
|
||||
import {
|
||||
getLocalizedDateFormatter,
|
||||
getLocalizedDurationFormatter
|
||||
} from '../base/i18n';
|
||||
} from '../base/i18n/dateUtil';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { NavigateSectionList } from '../base/react';
|
||||
import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
import { parseURIString, safeDecodeURIComponent } from '../base/util/uri';
|
||||
|
||||
import { IRecentItem } from './types';
|
||||
|
||||
/**
|
||||
* Creates a displayable list item of a recent list entry.
|
||||
@@ -14,7 +18,8 @@ import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
* @param {Function} t - The translate function.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function toDisplayableItem(item, defaultServerURL, t) {
|
||||
function toDisplayableItem(item: IRecentItem,
|
||||
defaultServerURL: string, t: Function) {
|
||||
const location = parseURIString(item.conference);
|
||||
const baseURL = `${location.protocol}//${location.host}`;
|
||||
const serverName = baseURL === defaultServerURL ? null : location.host;
|
||||
@@ -43,7 +48,7 @@ function toDisplayableItem(item, defaultServerURL, t) {
|
||||
* @param {number} duration - The item's duration.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _toDurationString(duration) {
|
||||
function _toDurationString(duration: number) {
|
||||
if (duration) {
|
||||
return getLocalizedDurationFormatter(duration);
|
||||
}
|
||||
@@ -59,7 +64,7 @@ function _toDurationString(duration) {
|
||||
* @param {Function} t - The translate function.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _toDateString(itemDate, t) {
|
||||
function _toDateString(itemDate: number, t: Function) {
|
||||
const m = getLocalizedDateFormatter(itemDate);
|
||||
const date = new Date(itemDate);
|
||||
const dateInMs = date.getTime();
|
||||
@@ -90,7 +95,8 @@ function _toDateString(itemDate, t) {
|
||||
* @param {string} defaultServerURL - The default server URL.
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
export function toDisplayableList(recentList, t, defaultServerURL) {
|
||||
export function toDisplayableList(recentList: IRecentItem[],
|
||||
t: Function, defaultServerURL: string) {
|
||||
const { createSection } = NavigateSectionList;
|
||||
const todaySection = createSection(t('dateUtils.today'), 'today');
|
||||
const yesterdaySection
|
||||
@@ -1,6 +1,4 @@
|
||||
/* global interfaceConfig */
|
||||
|
||||
import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
import { parseURIString, safeDecodeURIComponent } from '../base/util/uri';
|
||||
|
||||
|
||||
/**
|
||||
@@ -10,7 +8,7 @@ import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
* @param {Array<Object>} recentList - The recent list form the redux store.
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
export function toDisplayableList(recentList) {
|
||||
export function toDisplayableList(recentList: Array<{ conference: string; date: Date; duration: number; }>) {
|
||||
return (
|
||||
[ ...recentList ].reverse()
|
||||
.map(item => {
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/recent-list');
|
||||
@@ -1,21 +1,17 @@
|
||||
// @flow
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../base/app';
|
||||
import {
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
SET_ROOM
|
||||
} from '../base/conference';
|
||||
import { addKnownDomains } from '../base/known-domains';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { parseURIString } from '../base/util';
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference/actionTypes';
|
||||
import { JITSI_CONFERENCE_URL_KEY } from '../base/conference/constants';
|
||||
import { addKnownDomains } from '../base/known-domains/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { inIframe } from '../base/util/iframeUtils';
|
||||
import { parseURIString } from '../base/util/uri';
|
||||
|
||||
import { _storeCurrentConference, _updateConferenceDuration } from './actions';
|
||||
import { isRecentListEnabled } from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Middleware that captures joined rooms so they can be saved into
|
||||
* {@code window.localStorage}.
|
||||
@@ -53,7 +49,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {*} The result returned by {@code next(action)}.
|
||||
*/
|
||||
function _appWillMount({ dispatch, getState }, next, action) {
|
||||
function _appWillMount({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
// It's an opportune time to transfer the feature recent-list's knowledge
|
||||
@@ -86,7 +82,7 @@ function _appWillMount({ dispatch, getState }, next, action) {
|
||||
* @private
|
||||
* @returns {*} The result returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceWillLeave({ dispatch, getState }, next, action) {
|
||||
function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
const { doNotStoreRoom } = getState()['features/base/config'];
|
||||
|
||||
if (!doNotStoreRoom && !inIframe()) {
|
||||
@@ -126,7 +122,7 @@ function _conferenceWillLeave({ dispatch, getState }, next, action) {
|
||||
* @private
|
||||
* @returns {*} The result returned by {@code next(action)}.
|
||||
*/
|
||||
function _setRoom({ dispatch, getState }, next, action) {
|
||||
function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
const { doNotStoreRoom } = getState()['features/base/config'];
|
||||
|
||||
if (!doNotStoreRoom && !inIframe() && action.room) {
|
||||
5
react/features/recent-list/types.ts
Normal file
5
react/features/recent-list/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface IRecentItem {
|
||||
conference: string;
|
||||
date: number;
|
||||
duration: number;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import { getLocalJitsiDesktopTrack, getLocalJitsiVideoTrack } from '../../features/base/tracks';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../base/config';
|
||||
import { IStore } from '../app/types';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../base/config/functions';
|
||||
import { getLocalJitsiDesktopTrack, getLocalJitsiVideoTrack } from '../base/tracks/functions';
|
||||
|
||||
import { SET_SCREENSHOT_CAPTURE } from './actionTypes';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { createScreenshotCaptureSummary } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
let screenshotSummary;
|
||||
let screenshotSummary: any;
|
||||
|
||||
/**
|
||||
* Marks the on-off state of screenshot captures.
|
||||
@@ -18,7 +19,7 @@ let screenshotSummary;
|
||||
* payload: enabled
|
||||
* }}
|
||||
*/
|
||||
function setScreenshotCapture(enabled) {
|
||||
function setScreenshotCapture(enabled: boolean) {
|
||||
return {
|
||||
type: SET_SCREENSHOT_CAPTURE,
|
||||
payload: enabled
|
||||
@@ -32,7 +33,7 @@ function setScreenshotCapture(enabled) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function toggleScreenshotCaptureSummary(enabled: boolean) {
|
||||
return async function(dispatch: (Object) => Object, getState: () => any) {
|
||||
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
const state = getState();
|
||||
|
||||
if (state['features/screenshot-capture'].capturesEnabled !== enabled) {
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/screenshot-capture');
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { Share } from 'react-native';
|
||||
|
||||
import { getName } from '../app/functions';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { getShareInfoText } from '../invite';
|
||||
import { getName } from '../app/functions.native';
|
||||
import { IStore } from '../app/types';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { getShareInfoText } from '../invite/functions';
|
||||
|
||||
import { BEGIN_SHARE_ROOM } from './actionTypes';
|
||||
import { endShareRoom } from './actions';
|
||||
@@ -35,7 +34,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _shareRoom(roomURL: string, { dispatch, getState }) {
|
||||
function _shareRoom(roomURL: string, { dispatch, getState }: IStore) {
|
||||
getShareInfoText(getState(), roomURL)
|
||||
.then(message => {
|
||||
const title = `${getName()} Conference`;
|
||||
@@ -17,5 +17,17 @@ export function isWelcomePageEnabled(stateful: IStateful) {
|
||||
return getFeatureFlag(stateful, WELCOME_PAGE_ENABLED, false);
|
||||
}
|
||||
|
||||
return toState(stateful)['features/base/config'].enableWelcomePage;
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
return !config.welcomePage?.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured custom URL (if any) to redirect to instead of the normal landing page.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux state or {@link getState}.
|
||||
* @returns {string} - The custom URL.
|
||||
*/
|
||||
export function getCustomLandingPageURL(stateful: IStateful) {
|
||||
return toState(stateful)['features/base/config'].welcomePage?.customUrl;
|
||||
}
|
||||
|
||||
88
resources/extra-large-conference/README.md
Normal file
88
resources/extra-large-conference/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
WARNING: This is still a Work In Progress
|
||||
|
||||
The final implementation may diverge. Currently, only the participants after a
|
||||
configured threshold will be just viewers (visitors) and there is no promotion
|
||||
mechanism to become a main participant yet.
|
||||
|
||||
TODO:
|
||||
* Merge messaging between visitor nodes and main conference
|
||||
* Polls
|
||||
* Raise hand to be promoted to enter the main conference
|
||||
* Make sure it works with tenants.
|
||||
|
||||
|
||||
# Low-latency conference streaming to very large audiences
|
||||
|
||||
To have a low-latency conference with a very large audience, the media and
|
||||
signaling load must be spread beyond what can be handled by a typical Jitsi
|
||||
installation. A call with 10k participants requires around 50 bridges on decent
|
||||
vms (8+ cores). The main participants of a conference with a very large
|
||||
audience will share a main prosody, like with normal conferences, and
|
||||
additional prosody vms are needed to support signaling to the audience.
|
||||
|
||||
In the example configuration we use a 16 core machine. Eight of the cores are
|
||||
used for the main prosody and other services (nginx, jicofo, etc) and the other
|
||||
eight cores are used to run prosody services for visitors, i.e., "visitor
|
||||
prosodies".
|
||||
|
||||
We consider 2000 participants per visitor node a safe value. So eight visitor
|
||||
prosodies will be enough for one 10k participants meeting.
|
||||
|
||||
<img src="imgs/visitors-prosody.svg" alt="diagram of a central prosody connected to several visitor prosodies" width="500"/>
|
||||
|
||||
# Configuration
|
||||
Use the `pre-configure.sh` script to configure your system, passing it the
|
||||
number of visitor prosodies to set up.
|
||||
`./pre-configure.sh 8`
|
||||
|
||||
The script will add for each visitor prosody:
|
||||
- folders in `/etc/`
|
||||
- a systemd unit file in `/lib/systemd/system/`
|
||||
- a user for jicofo
|
||||
- a config entry in jicofo.conf
|
||||
|
||||
Setting up configuration for the main prosody is a manual process:
|
||||
- Add to the enabled modules list in the general part (e.g. [here](https://github.com/bjc/prosody/blob/76bf6d511f851c7cde8a81257afaaae0fb7a4160/prosody.cfg.lua.dist#L33)):
|
||||
```
|
||||
"s2s_bidi";
|
||||
"certs_s2soutinjection";
|
||||
"s2soutinjection";
|
||||
"s2s_whitelist";
|
||||
```
|
||||
|
||||
- Add the following config also in the general part (matching the number of prosodies you generated config for):
|
||||
```
|
||||
-- targets must be IPs, not hostnames
|
||||
s2s_connect_overrides = {
|
||||
["conference.v1.meet.jitsi"] = { "127.0.0.1", 52691 };
|
||||
["conference.v2.meet.jitsi"] = { "127.0.0.1", 52692 };
|
||||
["conference.v3.meet.jitsi"] = { "127.0.0.1", 52693 };
|
||||
["conference.v4.meet.jitsi"] = { "127.0.0.1", 52694 };
|
||||
["conference.v5.meet.jitsi"] = { "127.0.0.1", 52695 };
|
||||
["conference.v6.meet.jitsi"] = { "127.0.0.1", 52696 };
|
||||
["conference.v7.meet.jitsi"] = { "127.0.0.1", 52697 };
|
||||
["conference.v8.meet.jitsi"] = { "127.0.0.1", 52698 };
|
||||
}
|
||||
-- allowed list of server-2-server connections
|
||||
s2s_whitelist = {
|
||||
"conference.v1.meet.jitsi", "conference.v2.meet.jitsi", "conference.v3.meet.jitsi", "conference.v4.meet.jitsi",
|
||||
"conference.v5.meet.jitsi", "conference.v6.meet.jitsi", "conference.v7.meet.jitsi", "conference.v8.meet.jitsi"
|
||||
};
|
||||
```
|
||||
|
||||
- Make sure s2s is not in modules_disabled
|
||||
- Enable `"xxl_conference";` module under the main virtual host (e.g. [here](https://github.com/jitsi/jitsi-meet/blob/f42772ec5bcc87ff6de17423d36df9bcad6e770d/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example#L57))
|
||||
|
||||
After configuring you can set the maximum number of main participants, before
|
||||
redirecting to visitors.
|
||||
```
|
||||
hocon -f /etc/jitsi/jicofo/jicofo.conf set "jicofo.visitors.max-participants" 30
|
||||
```
|
||||
Now restart prosody and jicofo
|
||||
```
|
||||
service prosody restart
|
||||
service jicofo restart
|
||||
```
|
||||
|
||||
Now after the main 30 participants join, the rest will be visitors using the
|
||||
visitor nodes.
|
||||
2109
resources/extra-large-conference/imgs/visitors-prosody.svg
Normal file
2109
resources/extra-large-conference/imgs/visitors-prosody.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 198 KiB |
51
resources/extra-large-conference/pre-configure.sh
Executable file
51
resources/extra-large-conference/pre-configure.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR=`dirname "$0"`
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
NUMBER_OF_INSTANCES=$1
|
||||
|
||||
if ! [[ $NUMBER_OF_INSTANCES =~ ^[0-9]+([.][0-9]+)?$ ]] ; then
|
||||
echo "error: Not a number param" >&2;
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Will configure $NUMBER_OF_INSTANCES number of visitor prosodies"
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Configure prosody instances
|
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ ));
|
||||
do
|
||||
cp prosody-v.service.template /lib/systemd/system/prosody-v${i}.service
|
||||
sed -i "s/vX/v${i}/g" /lib/systemd/system/prosody-v${i}.service
|
||||
mkdir /etc/prosody-v${i}
|
||||
ln -s /etc/prosody/certs /etc/prosody-v${i}/certs
|
||||
cp prosody.cfg.lua.visitor.template /etc/prosody-v${i}/prosody.cfg.lua
|
||||
sed -i "s/vX/v${i}/g" /etc/prosody-v${i}/prosody.cfg.lua
|
||||
done
|
||||
|
||||
# Configure jicofo
|
||||
HOCON_CONFIG="/etc/jitsi/jicofo/jicofo.conf"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.selection-strategy" "VisitorSelectionStrategy"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.visitor-selection-strategy" "RegionBasedBridgeSelectionStrategy"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.bridge.topology-strategy" "VisitorTopologyStrategy"
|
||||
|
||||
PASS=$(hocon -f $HOCON_CONFIG get "jicofo.xmpp.client.password")
|
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ ));
|
||||
do
|
||||
prosodyctl --config /etc/prosody-v${i}/prosody.cfg.lua register focus auth.meet.jitsi $PASS
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.enabled" true
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.conference-service" "conference.v${i}.meet.jitsi"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.hostname" 127.0.0.1
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.port" 5222${i}
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.domain" "auth.meet.jitsi"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.password" "${PASS}"
|
||||
hocon -f $HOCON_CONFIG set "jicofo.xmpp.visitors.v${i}.disable-certificate-verification" true
|
||||
done
|
||||
|
||||
for (( i=1 ; i<=${NUMBER_OF_INSTANCES} ; i++ ));
|
||||
do
|
||||
service prosody-v${i} restart
|
||||
done
|
||||
service jicofo restart
|
||||
46
resources/extra-large-conference/prosody-v.service.template
Normal file
46
resources/extra-large-conference/prosody-v.service.template
Normal file
@@ -0,0 +1,46 @@
|
||||
[Unit]
|
||||
### see man systemd.unit
|
||||
Description=Prosody vX (visitor vX) JVB XMPP Server
|
||||
Documentation=https://prosody.im/doc
|
||||
|
||||
Requires=network-online.target
|
||||
After=network-online.target network.target mariadb.service mysql.service postgresql.service
|
||||
Before=biboumi.service
|
||||
|
||||
[Service]
|
||||
### see man systemd.service
|
||||
Type=simple
|
||||
|
||||
# Start by executing the main executable
|
||||
# Note: -F option requires Prosody 0.11.5 or later
|
||||
ExecStart=/usr/bin/prosody --config /etc/prosody-vX/prosody.cfg.lua -F
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-abnormal
|
||||
|
||||
### see man systemd.exec
|
||||
User=prosody
|
||||
Group=prosody
|
||||
UMask=0027
|
||||
|
||||
RuntimeDirectory=prosody-vX
|
||||
ConfigurationDirectory=prosody-vX
|
||||
StateDirectory=prosody-vX
|
||||
StateDirectoryMode=0750
|
||||
LogsDirectory=prosody-vX
|
||||
WorkingDirectory=~
|
||||
|
||||
# Set stdin to /dev/null since Prosody does not need it
|
||||
StandardInput=null
|
||||
|
||||
# Direct stdout/-err to journald for use with log = "*stdout"
|
||||
StandardOutput=journal
|
||||
StandardError=inherit
|
||||
|
||||
# Allow binding low ports
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
### see man systemd.unit
|
||||
WantedBy=multi-user.target
|
||||
|
||||
# vim: filetype=systemd
|
||||
@@ -0,0 +1,121 @@
|
||||
---------- Server-wide settings ----------
|
||||
s2s_ports = { 52691 };
|
||||
c2s_ports = { 52221 }
|
||||
http_ports = { 52801 }
|
||||
https_ports = { 52811 }
|
||||
|
||||
daemonize = true;
|
||||
|
||||
-- we use a common jid for jicofo
|
||||
admins = {
|
||||
'focus@auth.meet.jitsi'
|
||||
}
|
||||
|
||||
-- Enable use of native prosody 0.11 support for epoll over select
|
||||
network_backend = 'epoll';
|
||||
network_settings = {
|
||||
tcp_backlog = 511;
|
||||
}
|
||||
|
||||
modules_enabled = {
|
||||
'saslauth';
|
||||
'tls';
|
||||
'disco';
|
||||
'posix';
|
||||
|
||||
'secure_interfaces';
|
||||
|
||||
-- jitsi
|
||||
'websocket';
|
||||
'bosh';
|
||||
's2s_bidi';
|
||||
's2s_whitelist';
|
||||
};
|
||||
|
||||
s2s_whitelist = {};
|
||||
|
||||
external_service_secret = '__turnSecret__';
|
||||
|
||||
external_services = {
|
||||
{ type = 'stun', host = 'jitmeet.example.com', port = 3478 },
|
||||
{ type = 'turn', host = 'jitmeet.example.com', port = 3478, transport = 'udp', secret = true, ttl = 86400, algorithm = 'turn' },
|
||||
{ type = 'turns', host = 'jitmeet.example.com', port = 5349, transport = 'tcp', secret = true, ttl = 86400, algorithm = 'turn' }
|
||||
};
|
||||
|
||||
muc_mapper_domain_base = 'vX.meet.jitsi';
|
||||
|
||||
-- https://prosody.im/doc/modules/mod_smacks
|
||||
smacks_max_unacked_stanzas = 5;
|
||||
smacks_hibernation_time = 60;
|
||||
-- this is dropped in 0.12
|
||||
smacks_max_hibernated_sessions = 1;
|
||||
smacks_max_old_sessions = 1;
|
||||
|
||||
unlimited_jids = { 'focus@auth.meet.jitsi' }
|
||||
limits = {
|
||||
c2s = {
|
||||
rate = '512kb/s';
|
||||
};
|
||||
}
|
||||
|
||||
modules_disabled = {
|
||||
'offline';
|
||||
'pubsub';
|
||||
'register';
|
||||
};
|
||||
|
||||
allow_registration = false;
|
||||
authentication = 'internal_hashed'
|
||||
storage = 'internal'
|
||||
log = {
|
||||
-- Log files (change 'info' to 'debug' for debug logs):
|
||||
info = '/var/log/prosody-vX/prosody.log';
|
||||
error = '/var/log/prosody-vX/prosody.err';
|
||||
}
|
||||
|
||||
consider_websocket_secure = true;
|
||||
consider_bosh_secure = true;
|
||||
bosh_max_inactivity = 60;
|
||||
|
||||
plugin_paths = { '/usr/share/jitsi-meet/prosody-plugins/' }
|
||||
|
||||
----------- Virtual hosts -----------
|
||||
VirtualHost 'vX.meet.jitsi'
|
||||
authentication = 'jitsi-anonymous'
|
||||
ssl = {
|
||||
key = '/etc/prosody/certs/jitmeet.example.com.key';
|
||||
certificate = '/etc/prosody/certs/jitmeet.example.com.crt';
|
||||
}
|
||||
modules_enabled = {
|
||||
'bosh';
|
||||
'ping';
|
||||
'external_services';
|
||||
'smacks';
|
||||
'jiconop';
|
||||
}
|
||||
main_muc = 'conference.vX.meet.jitsi';
|
||||
|
||||
VirtualHost 'auth.meet.jitsi'
|
||||
modules_enabled = {
|
||||
'limits_exception';
|
||||
'ping';
|
||||
}
|
||||
authentication = 'internal_hashed'
|
||||
|
||||
Component 'conference.vX.meet.jitsi' 'muc'
|
||||
storage = 'memory'
|
||||
muc_room_cache_size = 10000
|
||||
restrict_room_creation = true
|
||||
modules_enabled = {
|
||||
'muc_hide_all';
|
||||
'muc_domain_mapper';
|
||||
'muc_meeting_id';
|
||||
'fmuc';
|
||||
}
|
||||
muc_room_default_presence_broadcast = {
|
||||
visitor = false;
|
||||
participant = true;
|
||||
moderator = true;
|
||||
};
|
||||
muc_room_locking = false
|
||||
muc_room_default_public_jids = true
|
||||
@@ -1,16 +0,0 @@
|
||||
-- validates all certificates, global module
|
||||
-- Warning: use this only for testing purposes as it will accept all kind of certificates for s2s connections
|
||||
-- you can use https://modules.prosody.im/mod_s2s_whitelist.html for whitelisting only certain destinations
|
||||
module:set_global();
|
||||
|
||||
function attach(event)
|
||||
local session = event.session;
|
||||
|
||||
session.cert_chain_status = 'valid';
|
||||
session.cert_identity_status = 'valid';
|
||||
|
||||
return true;
|
||||
end
|
||||
module:wrap_event('s2s-check-certificate', function (handlers, event_name, event_data)
|
||||
return attach(event_data);
|
||||
end);
|
||||
19
resources/prosody-plugins/mod_certs_s2soutinjection.lua
Normal file
19
resources/prosody-plugins/mod_certs_s2soutinjection.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- global module
|
||||
-- validates certificates for all hosts used for s2soutinjection
|
||||
module:set_global();
|
||||
|
||||
local s2s_overrides = module:get_option("s2s_connect_overrides");
|
||||
|
||||
function attach(event)
|
||||
local session = event.session;
|
||||
|
||||
if s2s_overrides and s2s_overrides[event.host] then
|
||||
session.cert_chain_status = 'valid';
|
||||
session.cert_identity_status = 'valid';
|
||||
|
||||
return true;
|
||||
end
|
||||
end
|
||||
module:wrap_event('s2s-check-certificate', function (handlers, event_name, event_data)
|
||||
return attach(event_data);
|
||||
end);
|
||||
@@ -7,12 +7,6 @@
|
||||
--- };
|
||||
--- Enable in global modules: 's2s_bidi'
|
||||
--- Make sure 's2s' is not in modules_disabled
|
||||
--- TODO: Do we need the /etc/hosts changes? We can drop it for https://modules.prosody.im/mod_s2soutinjection.html
|
||||
--- In /etc/hosts add:
|
||||
--- vmmain-ip-address focus.domain.com
|
||||
--- vmmain-ip-address conference.domain.com
|
||||
--- vmmain-ip-address domain.com
|
||||
--- Open port 5269 on the provider side and on the firewall of the machine, so the core node can access this visitor one
|
||||
local jid = require 'util.jid';
|
||||
local st = require 'util.stanza';
|
||||
|
||||
|
||||
20
resources/prosody-plugins/mod_s2s_whitelist.lua
Normal file
20
resources/prosody-plugins/mod_s2s_whitelist.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Using version https://hg.prosody.im/prosody-modules/file/c1a8ce147885/mod_s2s_whitelist/mod_s2s_whitelist.lua
|
||||
local st = require "util.stanza";
|
||||
|
||||
local whitelist = module:get_option_inherited_set("s2s_whitelist", {});
|
||||
|
||||
module:hook("route/remote", function (event)
|
||||
if not whitelist:contains(event.to_host) then
|
||||
module:send(st.error_reply(event.stanza, "cancel", "not-allowed", "Communication with this domain is restricted"));
|
||||
return true;
|
||||
end
|
||||
end, 100);
|
||||
|
||||
module:hook("s2s-stream-features", function (event)
|
||||
if not whitelist:contains(event.origin.from_host) then
|
||||
event.origin:close({
|
||||
condition = "policy-violation";
|
||||
text = "Communication with this domain is restricted";
|
||||
});
|
||||
end
|
||||
end, 1000);
|
||||
90
resources/prosody-plugins/mod_s2soutinjection.lua
Normal file
90
resources/prosody-plugins/mod_s2soutinjection.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
-- Using version https://hg.prosody.im/prosody-modules/file/4fb922aa0ace/mod_s2soutinjection/mod_s2soutinjection.lua
|
||||
local st = require"util.stanza";
|
||||
local new_outgoing = require"core.s2smanager".new_outgoing;
|
||||
local bounce_sendq = module:depends"s2s".route_to_new_session.bounce_sendq;
|
||||
local initialize_filters = require "util.filters".initialize;
|
||||
|
||||
local portmanager = require "core.portmanager";
|
||||
|
||||
local addclient = require "net.server".addclient;
|
||||
|
||||
module:depends("s2s");
|
||||
|
||||
local sessions = module:shared("sessions");
|
||||
|
||||
local injected = module:get_option("s2s_connect_overrides");
|
||||
|
||||
-- The proxy_listener handles connection while still connecting to the proxy,
|
||||
-- then it hands them over to the normal listener (in mod_s2s)
|
||||
local proxy_listener = { default_port = nil, default_mode = "*a", default_interface = "*" };
|
||||
|
||||
function proxy_listener.onconnect(conn)
|
||||
local session = sessions[conn];
|
||||
|
||||
-- Now the real s2s listener can take over the connection.
|
||||
local listener = portmanager.get_service("s2s").listener;
|
||||
|
||||
local log = session.log;
|
||||
|
||||
local filter = initialize_filters(session);
|
||||
|
||||
session.version = 1;
|
||||
|
||||
session.sends2s = function (t)
|
||||
log("debug", "sending (s2s over proxy): %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
|
||||
if t.name then
|
||||
t = filter("stanzas/out", t);
|
||||
end
|
||||
if t then
|
||||
t = filter("bytes/out", tostring(t));
|
||||
if t then
|
||||
return conn:write(tostring(t));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
session.open_stream = function ()
|
||||
session.sends2s(st.stanza("stream:stream", {
|
||||
xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
|
||||
["xmlns:stream"]='http://etherx.jabber.org/streams',
|
||||
from=session.from_host, to=session.to_host, version='1.0', ["xml:lang"]='en'}):top_tag());
|
||||
end
|
||||
|
||||
conn.setlistener(conn, listener);
|
||||
|
||||
listener.register_outgoing(conn, session);
|
||||
|
||||
listener.onconnect(conn);
|
||||
end
|
||||
|
||||
function proxy_listener.register_outgoing(conn, session)
|
||||
session.direction = "outgoing";
|
||||
sessions[conn] = session;
|
||||
end
|
||||
|
||||
function proxy_listener.ondisconnect(conn, err)
|
||||
sessions[conn] = nil;
|
||||
end
|
||||
|
||||
module:hook("route/remote", function(event)
|
||||
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
|
||||
local inject = injected and injected[to_host];
|
||||
if not inject then return end
|
||||
module:log("debug", "opening a new outgoing connection for this stanza");
|
||||
local host_session = new_outgoing(from_host, to_host);
|
||||
|
||||
-- Store in buffer
|
||||
host_session.bounce_sendq = bounce_sendq;
|
||||
host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
|
||||
host_session.log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name));
|
||||
|
||||
local host, port = inject[1] or inject, tonumber(inject[2]) or 5269;
|
||||
|
||||
local conn = addclient(host, port, proxy_listener, "*a");
|
||||
|
||||
proxy_listener.register_outgoing(conn, host_session);
|
||||
|
||||
host_session.conn = conn;
|
||||
return true;
|
||||
end, -2);
|
||||
|
||||
20
resources/prosody-plugins/mod_secure_interfaces.lua
Normal file
20
resources/prosody-plugins/mod_secure_interfaces.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Using version https://hg.prosody.im/prosody-modules/file/6c806a99f802/mod_secure_interfaces/mod_secure_interfaces.lua
|
||||
local secure_interfaces = module:get_option_set("secure_interfaces", { "127.0.0.1", "::1" });
|
||||
|
||||
module:hook("stream-features", function (event)
|
||||
local session = event.origin;
|
||||
if session.type ~= "c2s_unauthed" then return; end
|
||||
local socket = session.conn:socket();
|
||||
if not socket.getsockname then
|
||||
module:log("debug", "Unable to determine local address of incoming connection");
|
||||
return;
|
||||
end
|
||||
local localip = socket:getsockname();
|
||||
if secure_interfaces:contains(localip) then
|
||||
module:log("debug", "Marking session from %s to %s as secure", session.ip or "[?]", localip);
|
||||
session.secure = true;
|
||||
session.conn.starttls = false;
|
||||
else
|
||||
module:log("debug", "Not marking session from %s to %s as secure", session.ip or "[?]", localip);
|
||||
end
|
||||
end, 2500);
|
||||
@@ -5,6 +5,7 @@ local log = module._log;
|
||||
local host = module.host;
|
||||
local st = require "util.stanza";
|
||||
local um_is_admin = require "core.usermanager".is_admin;
|
||||
local jid_split = require "util.jid".split;
|
||||
|
||||
|
||||
local function is_admin(jid)
|
||||
@@ -39,8 +40,11 @@ log("debug",
|
||||
|
||||
-- option to disable room modification (sending muc config form) for guest that do not provide token
|
||||
local require_token_for_moderation;
|
||||
-- option to not require tokens for certain users and domains
|
||||
local token_empty_allow_list;
|
||||
local function load_config()
|
||||
require_token_for_moderation = module:get_option_boolean("token_verification_require_token_for_moderation");
|
||||
token_empty_allow_list = module:get_option_set("token_verification_empty_allow_list");
|
||||
end
|
||||
load_config();
|
||||
|
||||
@@ -57,6 +61,19 @@ local function verify_user(session, stanza)
|
||||
return true;
|
||||
end
|
||||
|
||||
-- if token is empty and user matches allow list, skip verification and allow user to join
|
||||
local user, domain, res = jid_split(user_jid);
|
||||
if session.auth_token == nil and user ~= nil and domain ~= nil and token_empty_allow_list then
|
||||
if token_empty_allow_list:contains(domain) then
|
||||
log("debug", "Token not required from user: %s in allowed domain: %s", user_jid, domain);
|
||||
return true;
|
||||
end
|
||||
if token_empty_allow_list:contains(user..'@'..domain) then
|
||||
log("debug", "Token not required from user in allowed list: %s", user_jid);
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
log("debug",
|
||||
"Will verify token for user: %s, room: %s ", user_jid, stanza.attr.to);
|
||||
if not token_util:verify_room(session, stanza.attr.to) then
|
||||
|
||||
@@ -56,29 +56,23 @@ local visitors_nodes = {};
|
||||
--- Jicofo is connected to the room when sending this error
|
||||
module:log('info', 'Hook to iq/host');
|
||||
module:hook('iq/full', function(event)
|
||||
local session, stanza = event.origin, event.stanza;
|
||||
local stanza = event.stanza;
|
||||
|
||||
if stanza.name ~= 'iq' or stanza.attr.type ~= 'error' or stanza.attr.from ~= focus_component_host then
|
||||
if stanza.name ~= 'iq' or stanza.attr.type ~= 'result' or stanza.attr.from ~= focus_component_host then
|
||||
return; -- not IQ from jicofo. Ignore this event.
|
||||
end
|
||||
|
||||
local error = stanza:get_child('error');
|
||||
if error == nil then
|
||||
return; -- not Conference IQ error. Ignore.
|
||||
end
|
||||
|
||||
local redirect = error:get_child('redirect', 'urn:ietf:params:xml:ns:xmpp-stanzas');
|
||||
local redirect_host = error:get_child_text('url', 'http://jitsi.org/jitmeet');
|
||||
|
||||
if not redirect or not redirect_host then
|
||||
local conference = stanza:get_child('conference', 'http://jitsi.org/protocol/focus');
|
||||
if not conference then
|
||||
return;
|
||||
end
|
||||
|
||||
-- let's send participants if any from the room to the visitors room
|
||||
-- TODO fix room name extract, make sure it works wit tenants
|
||||
local main_room = error:get_child_text('main-room', 'http://jitsi.org/jitmeet');
|
||||
local main_room = conference.attr.room;
|
||||
local vnode = conference.attr.vnode;
|
||||
|
||||
if not main_room then
|
||||
if not main_room or not vnode then
|
||||
return;
|
||||
end
|
||||
|
||||
@@ -88,7 +82,7 @@ module:hook('iq/full', function(event)
|
||||
return; -- room does not exists. Continue with normal flow
|
||||
end
|
||||
|
||||
local conference_service = muc_domain_prefix..'.'..redirect_host;
|
||||
local conference_service = muc_domain_prefix..'.'..vnode..'.meet.jitsi';
|
||||
|
||||
if visitors_nodes[room.jid] and
|
||||
visitors_nodes[room.jid].nodes and
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<!--#include virtual="/base.html" -->
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<script>
|
||||
window.EXCALIDRAW_ASSET_PATH = 'libs/';
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"react/features/share-room",
|
||||
"**/mobile/*",
|
||||
"**/native/*",
|
||||
"**/*.native.ts",
|
||||
|
||||
Reference in New Issue
Block a user