Example #1
0
    /**
     *
     */
    discoRoomInfo() {
        // https://xmpp.org/extensions/xep-0045.html#disco-roominfo

        const getInfo
            = $iq({
                type: 'get',
                to: this.roomjid
            })
                .c('query', { xmlns: Strophe.NS.DISCO_INFO });

        this.connection.sendIQ(getInfo, result => {
            const locked
                = $(result).find('>query>feature[var="muc_passwordprotected"]')
                    .length
                    === 1;

            if (locked !== this.locked) {
                this.eventEmitter.emit(XMPPEvents.MUC_LOCK_CHANGED, locked);
                this.locked = locked;
            }
        }, error => {
            GlobalOnErrorHandler.callErrorHandler(error);
            logger.error('Error getting room info: ', error);
        });
    }
    /**
     * Sends a jibri command using an iq.
     *
     * @private
     * @param {string} action - The action to send ('start' or 'stop').
     */
    _sendJibriIQ(action) {
        const attributes = {
            'xmlns': 'http://jitsi.org/protocol/jibri',
            'action': action,
            sipaddress: this.sipAddress
        };

        attributes.displayname = this.displayName;

        const iq = $iq({
            to: this.chatRoom.focusMucJid,
            type: 'set' })
            .c('jibri', attributes)
            .up();

        logger.debug(`${action} video SIP GW session`, iq.nodeTree);
        this.chatRoom.connection.sendIQ(
            iq,
            () => {}, // eslint-disable-line no-empty-function
            error => {
                logger.error(
                    `Failed to ${action} video SIP GW session, error: `, error);
                this.setState(VideoSIPGWConstants.STATE_FAILED);
            });
    }
Example #3
0
    /**
     *
     * @param fromJoin
     */
    sendPresence(fromJoin) {
        const to = this.presMap.to;

        if (!to || (!this.joined && !fromJoin)) {
            // Too early to send presence - not initialized
            return;
        }

        const pres = $pres({ to });

        // xep-0045 defines: "including in the initial presence stanza an empty
        // <x/> element qualified by the 'http://jabber.org/protocol/muc'
        // namespace" and subsequent presences should not include that or it can
        // be considered as joining, and server can send us the message history
        // for the room on every presence
        if (fromJoin) {
            pres.c('x', { xmlns: this.presMap.xns });

            if (this.password) {
                pres.c('password').t(this.password).up();
            }
            pres.up();
        }

        parser.json2packet(this.presMap.nodes, pres);
        this.connection.send(pres);
        if (fromJoin) {
            // XXX We're pressed for time here because we're beginning a complex
            // and/or lengthy conference-establishment process which supposedly
            // involves multiple RTTs. We don't have the time to wait for
            // Strophe to decide to send our IQ.
            this.connection.flush();
        }
    }
Moderator.prototype.logout = function(callback) {
    const iq = $iq({ to: this.getFocusComponent(),
        type: 'set' });
    const { sessionId } = Settings;

    if (!sessionId) {
        callback();

        return;
    }
    iq.c('logout', {
        xmlns: 'http://jitsi.org/protocol/focus',
        'session-id': sessionId
    });
    this.connection.sendIQ(
        iq,
        result => {
            // eslint-disable-next-line newline-per-chained-call
            let logoutUrl = $(result).find('logout').attr('logout-url');

            if (logoutUrl) {
                logoutUrl = decodeURIComponent(logoutUrl);
            }
            logger.info(`Log out OK, url: ${logoutUrl}`, result);
            Settings.sessionId = undefined;
            callback(logoutUrl);
        },
        error => {
            const errmsg = 'Logout error';

            GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
            logger.error(errmsg, error);
        }
    );
};
Example #5
0
        this.connection.sendIQ(getForm, form => {
            if (!$(form).find(
                    '>query>x[xmlns="jabber:x:data"]'
                    + '>field[var="muc#roomconfig_whois"]').length) {
                const errmsg = 'non-anonymous rooms not supported';

                GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
                logger.error(errmsg);

                return;
            }

            const formSubmit = $iq({ to: self.roomjid,
                type: 'set' })
                .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });

            formSubmit.c('x', { xmlns: 'jabber:x:data',
                type: 'submit' });

            formSubmit.c('field', { 'var': 'FORM_TYPE' })
                .c('value')
                .t('http://jabber.org/protocol/muc#roomconfig').up().up();

            formSubmit.c('field', { 'var': 'muc#roomconfig_whois' })
                .c('value').t('anyone').up().up();

            self.connection.sendIQ(formSubmit);

        }, error => {
Example #6
0
    /**
     *
     * @param subject
     */
    setSubject(subject) {
        const msg = $msg({ to: this.roomjid,
            type: 'groupchat' });

        msg.c('subject', subject);
        this.connection.send(msg);
    }
Example #7
0
    /* eslint-disable max-params */

    /**
     *
     * @param key
     * @param onSuccess
     * @param onError
     * @param onNotSupported
     */
    lockRoom(key, onSuccess, onError, onNotSupported) {
        // http://xmpp.org/extensions/xep-0045.html#roomconfig
        this.connection.sendIQ(
            $iq({
                to: this.roomjid,
                type: 'get'
            })
                .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }),
            res => {
                if ($(res)
                        .find(
                            '>query>x[xmlns="jabber:x:data"]'
                                + '>field[var="muc#roomconfig_roomsecret"]')
                        .length) {
                    const formsubmit
                        = $iq({
                            to: this.roomjid,
                            type: 'set'
                        })
                            .c('query', {
                                xmlns: 'http://jabber.org/protocol/muc#owner'
                            });

                    formsubmit.c('x', {
                        xmlns: 'jabber:x:data',
                        type: 'submit'
                    });
                    formsubmit
                        .c('field', { 'var': 'FORM_TYPE' })
                        .c('value')
                        .t('http://jabber.org/protocol/muc#roomconfig')
                        .up()
                        .up();
                    formsubmit
                        .c('field', { 'var': 'muc#roomconfig_roomsecret' })
                        .c('value')
                        .t(key)
                        .up()
                        .up();

                    // Fixes a bug in prosody 0.9.+
                    // https://prosody.im/issues/issue/373
                    formsubmit
                        .c('field', { 'var': 'muc#roomconfig_whois' })
                        .c('value')
                        .t('anyone')
                        .up()
                        .up();

                    // FIXME: is muc#roomconfig_passwordprotectedroom required?
                    this.connection.sendIQ(formsubmit, onSuccess, onError);
                } else {
                    onNotSupported();
                }
            },
            onError);
    }
    /* eslint-disable max-params */

    /**
     * Sends "ping" to given <tt>jid</tt>
     * @param jid the JID to which ping request will be sent.
     * @param success callback called on success.
     * @param error callback called on error.
     * @param timeout ms how long are we going to wait for the response. On
     * timeout <tt>error<//t> callback is called with undefined error argument.
     */
    ping(jid, success, error, timeout) {
        this._addPingExecutionTimestamp();

        const iq = $iq({
            type: 'get',
            to: jid
        });

        iq.c('ping', { xmlns: Strophe.NS.PING });
        this.connection.sendIQ(iq, success, error, timeout);
    }
Example #9
0
    /**
     *
     * @param jid
     */
    kick(jid) {
        const kickIQ = $iq({ to: this.roomjid,
            type: 'set' })
            .c('query', { xmlns: 'http://jabber.org/protocol/muc#admin' })
            .c('item', { nick: Strophe.getResourceFromJid(jid),
                role: 'none' })
            .c('reason').t('You have been kicked.').up().up().up();

        this.connection.sendIQ(
            kickIQ,
            result => logger.log('Kick participant with jid: ', jid, result),
            error => logger.log('Kick participant error: ', error));
    }
Example #10
0
Recording.prototype.setRecordingJibri = function(
        state,
        callback,
        errCallback,
        options = {}) {
    if (state === this.state) {
        errCallback(JitsiRecorderErrors.INVALID_STATE);
    }

    // FIXME jibri does not accept IQ without 'url' attribute set ?
    const iq
        = $iq({
            to: this.focusMucJid,
            type: 'set'
        })
            .c('jibri', {
                'xmlns': 'http://jitsi.org/protocol/jibri',
                'action':
                    state === Recording.status.ON
                        ? Recording.action.START
                        : Recording.action.STOP,
                'recording_mode':
                    this.type === Recording.types.JIBRI_FILE
                        ? 'file'
                        : 'stream',
                'streamid':
                    this.type === Recording.types.JIBRI
                        ? options.streamId
                        : undefined
            })
            .up();

    logger.log(`Set jibri recording: ${state}`, iq.nodeTree);
    logger.log(iq.nodeTree);
    this.connection.sendIQ(
        iq,
        result => {
            logger.log('Result', result);

            const jibri = $(result).find('jibri');

            callback(jibri.attr('state'), jibri.attr('url'));
        },
        error => {
            logger.log(
                'Failed to start recording, error: ',
                getJibriErrorDetails(error));
            errCallback(error);
        });
};
Example #11
0
    /**
     * Send text message to the other participants in the conference
     * @param body
     * @param nickname
     */
    sendMessage(body, nickname) {
        const msg = $msg({ to: this.roomjid,
            type: 'groupchat' });

        msg.c('body', body).up();
        if (nickname) {
            msg.c('nick', { xmlns: 'http://jabber.org/protocol/nick' })
                .t(nickname)
                .up()
                .up();
        }
        this.connection.send(msg);
        this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
    }
 /**
  * Generates the message to change the status of the recording session.
  *
  * @param {string} status - The new status to which the recording session
  * should transition.
  * @param {string} [options.appData] - Data specific to the app/service that
  * the result file will be uploaded.
  * @param {string} [options.broadcastId] - The broadcast ID of an
  * associated YouTube stream, used for knowing the URL from which the stream
  * can be viewed.
  * @param {string} options.focusMucJid - The JID of the focus participant
  * that controls recording.
  * @param {streamId} options.streamId - Necessary for live streaming, this
  * is the the stream key needed to start a live streaming session with the
  * streaming service provider.
  * @returns Object - The XMPP IQ message.
  */
 _createIQ({ action, appData, broadcastId, focusMucJid, streamId }) {
     return $iq({
         to: focusMucJid,
         type: 'set'
     })
     .c('jibri', {
         'xmlns': 'http://jitsi.org/protocol/jibri',
         'action': action,
         'app_data': appData,
         'recording_mode': this._mode,
         'streamid': streamId,
         'you_tube_broadcast_id': broadcastId
     })
     .up();
 }
Example #13
0
    /**
     * Send private text message to another participant of the conference
     * @param id id/muc resource of the receiver
     * @param body
     * @param nickname
     */
    sendPrivateMessage(id, body, nickname) {
        const msg = $msg({ to: `${this.roomjid}/${id}`,
            type: 'chat' });

        msg.c('body', body).up();
        if (nickname) {
            msg.c('nick', { xmlns: 'http://jabber.org/protocol/nick' })
                .t(nickname)
                .up()
                .up();
        }

        this.connection.send(msg);
        this.eventEmitter.emit(XMPPEvents.SENDING_PRIVATE_CHAT_MESSAGE, body);
    }
Example #14
0
Recording.prototype.setRecordingColibri = function(
        state,
        callback,
        errCallback,
        options) {
    const elem = $iq({
        to: this.focusMucJid,
        type: 'set'
    });

    elem.c('conference', {
        xmlns: 'http://jitsi.org/protocol/colibri'
    });
    elem.c('recording', {
        state,
        token: options.token
    });

    const self = this;

    this.connection.sendIQ(
        elem,
        result => {
            logger.log('Set recording "', state, '". Result:', result);
            const recordingElem = $(result).find('>conference>recording');
            const newState = recordingElem.attr('state');

            self.state = newState;
            callback(newState);

            if (newState === 'pending') {
                self.connection.addHandler(iq => {
                    // eslint-disable-next-line newline-per-chained-call
                    const s = $(iq).find('recording').attr('state');

                    if (s) {
                        self.state = newState;
                        callback(s);
                    }
                }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
            }
        },
        error => {
            logger.warn(error);
            errCallback(error);
        }
    );
};
Example #15
0
    = function(state, callback, errCallback) {
        if (state === this.state) {
            errCallback(new Error('Invalid state!'));
        }

        const iq
            = $iq({
                to: this.jirecon,
                type: 'set'
            })
                .c('recording', { xmlns: 'http://jitsi.org/protocol/jirecon',
                    action: state === Recording.status.ON
                        ? Recording.action.START
                        : Recording.action.STOP,
                    mucjid: this.roomjid });

        if (state === Recording.status.OFF) {
            iq.attrs({ rid: this.jireconRid });
        }

        logger.log('Start recording');
        const self = this;

        this.connection.sendIQ(
            iq,
            result => {
                // TODO wait for an IQ with the real status, since this is
                // provisional?
                // eslint-disable-next-line newline-per-chained-call
                self.jireconRid = $(result).find('recording').attr('rid');

                const stateStr
                    = state === Recording.status.ON ? 'started' : 'stopped';

                logger.log(`Recording ${stateStr}(jirecon)${result}`);

                self.state = state;
                if (state === Recording.status.OFF) {
                    self.jireconRid = null;
                }

                callback(state);
            },
            error => {
                logger.log('Failed to start recording, error: ', error);
                errCallback(error);
            });
    };
Example #16
0
    /**
     * Mutes remote participant.
     * @param jid of the participant
     * @param mute
     */
    muteParticipant(jid, mute) {
        logger.info('set mute', mute);
        const iqToFocus = $iq(
            { to: this.focusMucJid,
                type: 'set' })
            .c('mute', {
                xmlns: 'http://jitsi.org/jitmeet/audio',
                jid
            })
            .t(mute.toString())
            .up();

        this.connection.sendIQ(
            iqToFocus,
            result => logger.log('set mute', result),
            error => logger.log('set mute error', error));
    }
Moderator.prototype._getLoginUrl = function(popup, urlCb, failureCb) {
    const iq = $iq({ to: this.getFocusComponent(),
        type: 'get' });
    const attrs = {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': Settings.machineId
    };
    let str = 'auth url'; // for logger

    if (popup) {
        attrs.popup = true;
        str = `POPUP ${str}`;
    }
    iq.c('login-url', attrs);

    /**
     * Implements a failure callback which reports an error message and an error
     * through (1) GlobalOnErrorHandler, (2) logger, and (3) failureCb.
     *
     * @param {string} errmsg the error messsage to report
     * @param {*} error the error to report (in addition to errmsg)
     */
    function reportError(errmsg, err) {
        GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
        logger.error(errmsg, err);
        failureCb(err);
    }
    this.connection.sendIQ(
        iq,
        result => {
            // eslint-disable-next-line newline-per-chained-call
            let url = $(result).find('login-url').attr('url');

            url = decodeURIComponent(url);
            if (url) {
                logger.info(`Got ${str}: ${url}`);
                urlCb(url);
            } else {
                reportError(`Failed to get ${str} from the focus`, result);
            }
        },
        reportError.bind(undefined, `Get ${str} error`)
    );
};
Example #18
0
    /**
     *
     */
    createNonAnonymousRoom() {
        // http://xmpp.org/extensions/xep-0045.html#createroom-reserved

        const getForm = $iq({ type: 'get',
            to: this.roomjid })
            .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' })
            .c('x', { xmlns: 'jabber:x:data',
                type: 'submit' });

        const self = this;

        this.connection.sendIQ(getForm, form => {
            if (!$(form).find(
                    '>query>x[xmlns="jabber:x:data"]'
                    + '>field[var="muc#roomconfig_whois"]').length) {
                const errmsg = 'non-anonymous rooms not supported';

                GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
                logger.error(errmsg);

                return;
            }

            const formSubmit = $iq({ to: self.roomjid,
                type: 'set' })
                .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });

            formSubmit.c('x', { xmlns: 'jabber:x:data',
                type: 'submit' });

            formSubmit.c('field', { 'var': 'FORM_TYPE' })
                .c('value')
                .t('http://jabber.org/protocol/muc#roomconfig').up().up();

            formSubmit.c('field', { 'var': 'muc#roomconfig_whois' })
                .c('value').t('anyone').up().up();

            self.connection.sendIQ(formSubmit);

        }, error => {
            GlobalOnErrorHandler.callErrorHandler(error);
            logger.error('Error getting room configuration form: ', error);
        });
    }
Example #19
0
    /**
     * Sends the presence unavailable, signaling the server
     * we want to leave the room.
     */
    doLeave() {
        logger.log('do leave', this.myroomjid);
        const pres = $pres({ to: this.myroomjid,
            type: 'unavailable' });

        this.presMap.length = 0;

        // XXX Strophe is asynchronously sending by default. Unfortunately, that
        // means that there may not be enough time to send the unavailable
        // presence. Switching Strophe to synchronous sending is not much of an
        // option because it may lead to a noticeable delay in navigating away
        // from the current location. As a compromise, we will try to increase
        // the chances of sending the unavailable presence within the short time
        // span that we have upon unloading by invoking flush() on the
        // connection. We flush() once before sending/queuing the unavailable
        // presence in order to attemtp to have the unavailable presence at the
        // top of the send queue. We flush() once more after sending/queuing the
        // unavailable presence in order to attempt to have it sent as soon as
        // possible.
        this.connection.flush();
        this.connection.send(pres);
        this.connection.flush();
    }
Moderator.prototype.createConferenceIq = function() {
    // Generate create conference IQ
    const elem = $iq({ to: this.getFocusComponent(),
        type: 'set' });

    // Session Id used for authentication
    const { sessionId } = Settings;
    const machineUID = Settings.machineId;
    const config = this.options.conference;

    logger.info(`Session ID: ${sessionId} machine UID: ${machineUID}`);

    elem.c('conference', {
        xmlns: 'http://jitsi.org/protocol/focus',
        room: this.roomName,
        'machine-uid': machineUID
    });

    if (sessionId) {
        elem.attrs({ 'session-id': sessionId });
    }
    if (this.options.connection.enforcedBridge !== undefined) {
        elem.c(
            'property', {
                name: 'enforcedBridge',
                value: this.options.connection.enforcedBridge
            }).up();
    }

    // Tell the focus we have Jigasi configured
    if (this.options.connection.hosts !== undefined
        && this.options.connection.hosts.call_control !== undefined) {
        elem.c(
            'property', {
                name: 'call_control',
                value: this.options.connection.hosts.call_control
            }).up();
    }
    if (config.channelLastN !== undefined) {
        elem.c(
            'property', {
                name: 'channelLastN',
                value: config.channelLastN
            }).up();
    }
    elem.c(
        'property', {
            name: 'disableRtx',
            value: Boolean(config.disableRtx)
        }).up();

    if (config.enableTcc !== undefined) {
        elem.c(
                'property', {
                    name: 'enableTcc',
                    value: Boolean(config.enableTcc)
                }).up();
    }
    if (config.enableRemb !== undefined) {
        elem.c(
                'property', {
                    name: 'enableRemb',
                    value: Boolean(config.enableRemb)
                }).up();
    }
    if (config.minParticipants !== undefined) {
        elem.c(
                'property', {
                    name: 'minParticipants',
                    value: config.minParticipants
                }).up();
    }

    elem.c(
        'property', {
            name: 'enableLipSync',
            value: this.options.connection.enableLipSync !== false
        }).up();
    if (config.audioPacketDelay !== undefined) {
        elem.c(
            'property', {
                name: 'audioPacketDelay',
                value: config.audioPacketDelay
            }).up();
    }
    if (config.startBitrate) {
        elem.c(
            'property', {
                name: 'startBitrate',
                value: config.startBitrate
            }).up();
    }
    if (config.minBitrate) {
        elem.c(
            'property', {
                name: 'minBitrate',
                value: config.minBitrate
            }).up();
    }
    if (config.testing && config.testing.octo
        && typeof config.testing.octo.probability === 'number') {
        if (Math.random() < config.testing.octo.probability) {
            elem.c(
                'property', {
                    name: 'octo',
                    value: true
                }).up();
        }
    }

    let openSctp;

    switch (this.options.conference.openBridgeChannel) {
    case 'datachannel':
    case true:
    case undefined:
        openSctp = true;
        break;
    case 'websocket':
        openSctp = false;
        break;
    }

    if (openSctp && !browser.supportsDataChannels()) {
        openSctp = false;
    }

    elem.c(
        'property', {
            name: 'openSctp',
            value: openSctp
        }).up();

    if (this.options.conference.startAudioMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startAudioMuted',
                value: this.options.conference.startAudioMuted
            }).up();
    }
    if (this.options.conference.startVideoMuted !== undefined) {
        elem.c(
            'property', {
                name: 'startVideoMuted',
                value: this.options.conference.startVideoMuted
            }).up();
    }
    if (this.options.conference.stereo !== undefined) {
        elem.c(
            'property', {
                name: 'stereo',
                value: this.options.conference.stereo
            }).up();
    }
    if (this.options.conference.useRoomAsSharedDocumentName !== undefined) {
        elem.c(
            'property', {
                name: 'useRoomAsSharedDocumentName',
                value: this.options.conference.useRoomAsSharedDocumentName
            }).up();
    }
    elem.up();

    return elem;
};