Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Audio video capability #2956

Draft
wants to merge 28 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5e5fadc
jingle: added the initial boilerplate code for the plugin.
PawBud Jun 16, 2022
387e781
(TBR)CallButton: added an unfunctional call button to the toolbar
PawBud Jun 17, 2022
13b1ef9
(TBR)index: added buttons hook to jingle plugin
PawBud Jun 20, 2022
6d88243
test
PawBud Jun 20, 2022
fb84f10
revert back code to avoid merge conflicts
PawBud Jun 20, 2022
d31b485
(TBR)added a boilerplate modal for jingle call
PawBud Jun 21, 2022
5c4b40d
added the call button at the chat header.
PawBud Jun 23, 2022
3f975d2
(TBR)Jingle modal
PawBud Jun 23, 2022
70a49d3
(TBR)added 2 new custom plugins for jingle
PawBud Jun 27, 2022
309c29d
added a toggling button in the centre of the chat-header
PawBud Jun 29, 2022
97b737d
jingle: added the tests for jingle & styled the header button
PawBud Jun 30, 2022
4eb696e
additional tests for jingle
PawBud Jun 30, 2022
1f6bd12
tests for chat header written
PawBud Jul 4, 2022
cbba9cb
(TBR) added the message initation tests
PawBud Jul 6, 2022
0e19aee
(TBR)added the Incoming pending & outgoing pending states, xep 0353 t…
PawBud Jul 6, 2022
ee9da60
tests: added tests to message initiation
PawBud Jul 10, 2022
6c0f422
(TBR)propose id is now shared with the retract stanza
PawBud Jul 15, 2022
322af18
(TBR) corrected message initiation test
PawBud Jul 18, 2022
bbcded7
changed the describe function text
PawBud Jul 18, 2022
c978385
changed the describe function text
PawBud Jul 19, 2022
1140ad8
(TBR) corrected some tests
PawBud Jul 24, 2022
bbb985e
(TBR) fixed the tests and added the On message hook
PawBud Jul 26, 2022
1235d42
(TBR)added the message retraction feature
PawBud Aug 4, 2022
b3ace4d
chat history: this commit adds the chat history of the jingle call st…
PawBud Aug 8, 2022
4c5b855
(TRB) Retraction improvements
PawBud Aug 24, 2022
7ef19af
(TBR)added a hook for the retraction
PawBud Aug 29, 2022
bec6455
The Reciever's side retraction tests pass
PawBud Aug 30, 2022
8f72210
chat history ui added and tests as well
PawBud Sep 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ The core, and by default whitelisted, plugins are::
converse-dragresize
converse-fullscreen
converse-headline
converse-jingle
converse-mam
converse-minimize
converse-muc
Expand Down
1 change: 1 addition & 0 deletions docs/source/other_frameworks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Below is an example code that wraps converse.js as an angular.js service.
"converse-vcard", // XEP-0054 VCard-temp
"converse-register", // XEP-0077 In-band registration
"converse-ping", // XEP-0199 XMPP Ping
"converse-jingle", // XEP-0166 Support for the Jingle Protocol
"converse-notification", // HTML5 Notifications
"converse-minimize", // Allows chatboxes to be minimized
"converse-dragresize", // Allows chatboxes to be resized by dragging them
Expand Down
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ module.exports = function(config) {
{ pattern: "src/plugins/controlbox/tests/controlbox.js", type: 'module' },
{ pattern: "src/plugins/controlbox/tests/login.js", type: 'module' },
{ pattern: "src/plugins/headlines-view/tests/headline.js", type: 'module' },
{ pattern: "src/plugins/jingle/tests/ui.js", type: 'module' },
{ pattern: "src/plugins/jingle/tests/message-initiation.js", type: 'module' },
{ pattern: "src/plugins/mam-views/tests/mam.js", type: 'module' },
{ pattern: "src/plugins/mam-views/tests/placeholder.js", type: 'module' },
{ pattern: "src/plugins/minimize/tests/minchats.js", type: 'module' },
Expand Down
1 change: 1 addition & 0 deletions src/converse.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import "./plugins/controlbox/index.js"; // The control box
import "./plugins/dragresize/index.js"; // Allows chat boxes to be resized by dragging them
import "./plugins/fullscreen/index.js";
import "./plugins/headlines-view/index.js";
import "./plugins/jingle/index.js" // Implements the jingle protocol
import "./plugins/mam-views/index.js";
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
import "./plugins/muc-views/index.js"; // Views related to MUC
Expand Down
1 change: 1 addition & 0 deletions src/headless/plugins/chat/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ const ChatBox = ModelWithContact.extend({
!this.handleChatMarker(attrs) &&
!(await this.handleRetraction(attrs))
) {
const message_retraction = await api.hook('onMessage', this, { retract: false });
PawBud marked this conversation as resolved.
Show resolved Hide resolved
this.setEditable(attrs, attrs.time);

if (attrs['chat_state'] && attrs.sender === 'them') {
Expand Down
4 changes: 2 additions & 2 deletions src/headless/plugins/chat/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ export async function handleMessageStanza (stanza) {
return log.error(attrs.message);
}
// XXX: Need to take XEP-428 <fallback> into consideration
const has_body = !!(attrs.body || attrs.plaintext)
const chatbox = await api.chats.get(attrs.contact_jid, { 'nickname': attrs.nick }, has_body);
const should_create = !!(attrs.body || attrs.plaintext || attrs.jingle_propose)
const chatbox = await api.chats.get(attrs.contact_jid, { 'nickname': attrs.nick }, should_create);
await chatbox?.queueMessage(attrs);
/**
* @typedef { Object } MessageData
Expand Down
2 changes: 1 addition & 1 deletion src/headless/utils/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function isEmptyMessage (attrs) {
return !attrs['oob_url'] &&
!attrs['file'] &&
!(attrs['is_encrypted'] && attrs['plaintext']) &&
!attrs['message'];
!attrs['message'] && !attrs['jingle_propose'];
}

/* We distinguish between UniView and MultiView instances.
Expand Down
1 change: 1 addition & 0 deletions src/plugins/chatview/heading.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default class ChatHeading extends CustomElement {

initialize () {
this.model = _converse.chatboxes.get(this.jid);
this.listenTo(this.model, 'change:jingle_status', this.requestUpdate);
this.listenTo(this.model, 'change:status', this.requestUpdate);
this.listenTo(this.model, 'vcard:add', this.requestUpdate);
this.listenTo(this.model, 'vcard:change', this.requestUpdate);
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/chatview/styles/chat-head.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
flex-wrap: nowrap;
padding: 0;
}

.chatbox-call-status {
width: 80%;
}

a, a:visited, a:hover, a:not([href]):not([tabindex]) {
&.chatbox-btn {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/chatview/templates/chat-head.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default (o) => {
<div class="chatbox-title__text" title="${o.jid}">
${ (o.type !== _converse.HEADLINES_TYPE) ? html`<a class="user show-msg-author-modal" @click=${o.showUserDetailsModal}>${ display_name }</a>` : display_name }
</div>
<converse-call-notification class="d-flex flex-row-reverse justify-content-center chatbox-call-status chatbox-title__text" jid=${o.model.get('jid')}></converse-call-notification>
</div>
<div class="chatbox-title__buttons row no-gutters">
${ until(tpl_dropdown_btns(), '') }
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/chatview/tests/chatbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ describe("Chatboxes", function () {


it("can contain a button for starting a call",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
mock.initConverse(['chatBoxesFetched'], { blacklisted_plugins: ['converse-jingle']}, async function (_converse) {

const { api } = _converse;
await mock.waitForRoster(_converse, 'current');
Expand Down
73 changes: 73 additions & 0 deletions src/plugins/jingle/chat-header-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { CustomElement } from 'shared/components/element.js';
import { _converse, api, converse } from "@converse/headless/core";
import tpl_header_button from "./templates/header-button.js";
import { JINGLE_CALL_STATUS } from "./constants.js";

import './styles/jingle.scss';
const { Strophe, $msg } = converse.env;
const u = converse.env.utils;

export default class CallNotification extends CustomElement {

static get properties() {
return {
'jid': { type: String },
}
}

initialize() {
this.model = _converse.chatboxes.get(this.jid);
this.listenTo(this.model, 'change:jingle_status', () => this.requestUpdate());
}

render() {
return tpl_header_button(this);
}

endCall() {
const jingle_status = this.model.get('jingle_status');
if ( jingle_status === JINGLE_CALL_STATUS.OUTGOING_PENDING ) {
this.model.save('jingle_status', JINGLE_CALL_STATUS.ENDED);
const initiator_stanza = this.model.messages.findWhere({ 'media': 'audio' });
PawBud marked this conversation as resolved.
Show resolved Hide resolved
const propose_id = initiator_stanza.attributes.propose_id;
const message_id = u.getUniqueId();
api.send(
$msg({
'from': _converse.bare_jid,
'to': this.jid,
'type': 'chat',
id: message_id
}).c('retract', { 'xmlns': Strophe.NS.JINGLEMESSAGE, 'id': propose_id })
.c('reason', { 'xmlns': Strophe.NS.JINGLE })
.c('cancel', {}).up()
.t('Retracted').up().up()
.c('store', { 'xmlns': Strophe.NS.HINTS })
);
const attrs = {
'from': _converse.bare_jid,
'to': this.jid,
'type': 'chat',
'retract_id': propose_id,
'msg_id': message_id
}
this.model.messages.create(attrs);
return;
}
if ( jingle_status === JINGLE_CALL_STATUS.ACTIVE) {
this.model.save('jingle_status', JINGLE_CALL_STATUS.ENDED);
const stanza = $msg({
'from': _converse.bare_jid,
'to': this.jid,
'type': 'chat'
}).c('finish', {'xmlns': Strophe.NS.JINGLEMESSAGE, 'id': this.getAttribute('id')})
.c('reason', {'xmlns': Strophe.NS.JINGLE})
.c('success', {}).up()
.t('Success').up().up()
.c('store', {'xmlns': Strophe.NS.HINTS})
api.send(stanza);
return;
}
}
}

api.elements.define('converse-call-notification', CallNotification);
6 changes: 6 additions & 0 deletions src/plugins/jingle/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const JINGLE_CALL_STATUS = {
INCOMING_PENDING: 0,
OUTGOING_PENDING: 1,
ACTIVE: 2,
ENDED: 3
};
52 changes: 52 additions & 0 deletions src/plugins/jingle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @description Converse.js plugin which adds XEP-0166 Jingle
* @copyright 2022, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/

import { _converse, converse, api } from '@converse/headless/core.js';
import 'plugins/modal/index.js';
import "./chat-header-notification.js";
import './toolbar-button.js';
import { JINGLE_CALL_STATUS } from './constants.js';
import { html } from "lit";
import { parseJingleMessage, handleRetraction } from './utils.js';

const { Strophe } = converse.env;

Strophe.addNamespace('JINGLE', 'urn:xmpp:jingle:1');
Strophe.addNamespace('JINGLEMESSAGE', 'urn:xmpp:jingle-message:1');
Strophe.addNamespace('JINGLERTP', 'urn:xmpp:jingle:apps:rtp:1');

converse.plugins.add('converse-jingle', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ['converse-chatview'],

initialize: function () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
_converse.JINGLE_CALL_STATUS = JINGLE_CALL_STATUS;
_converse.api.listen.on('getToolbarButtons', (toolbar_el, buttons) => {
if (!this.is_groupchat) {
buttons.push(html`
<converse-jingle-toolbar-button jid=${toolbar_el.model.get('jid')}>
</converse-jingle-toolbar-button>
`);
}

return buttons;
});
api.listen.on('parseMessage', parseJingleMessage);
api.listen.on('onMessage', handleRetraction);
},
});
18 changes: 18 additions & 0 deletions src/plugins/jingle/modal/jingle-incoming-call-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import BootstrapModal from "plugins/modal/base.js";
import tpl_incoming_call from "../templates/incoming-call.js";

export default BootstrapModal.extend({
id: "start-jingle-call-modal",
persistent: true,

initialize () {
this.items = [];
this.loading_items = false;

BootstrapModal.prototype.initialize.apply(this, arguments);
},

toHTML () {
return tpl_incoming_call();
}
});
9 changes: 9 additions & 0 deletions src/plugins/jingle/styles/jingle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.conversejs {
.chatbox {
.chat-head {
.jingle-call-initiated-button{
color: var(--chat-head-text-color) !important;
}
}
}
}
26 changes: 26 additions & 0 deletions src/plugins/jingle/templates/header-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { html } from 'lit';
import { __ } from 'i18n';
import { JINGLE_CALL_STATUS } from '../constants';

const tpl_active_call = (o) => {
const button = __('End Call');
return html`
<div>
<a class="jingle-call-initiated-button" @click=${o.endCall}>${ button }</a>
</div>
`;
}

// ${(jingle_status === JINGLE_CALL_STATUS.ACTIVE) ? html`${tpl_active_call(el)}` : html`` }
export default (el) => {
const jingle_status = el.model.get('jingle_status');
return html`
<div>
${(jingle_status === JINGLE_CALL_STATUS.OUTGOING_PENDING) ? html`Calling...` : '' }
</div>
<div>
${(jingle_status === JINGLE_CALL_STATUS.OUTGOING_PENDING) ? tpl_active_call(el) : '' }
${(jingle_status === JINGLE_CALL_STATUS.ENDED) ? html`Call Ended` : '' }
</div>
`;
}
27 changes: 27 additions & 0 deletions src/plugins/jingle/templates/incoming-call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { html } from 'lit';
import { __ } from 'i18n';

const modal_close_button = html`<button type="button" class="btn btn-secondary" data-dismiss="modal">${__('Close')}</button>`;

export default () => {
const i18n_modal_title = __('Jingle Call');
return html`
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="muc-list-modal-label">${i18n_modal_title}</h5>
</div>
<div class="modal-body d-flex flex-column">
<span class="modal-alert"></span>
<ul class="available-chatrooms list-group">
</ul>
</div>
<div class="container text-center cl-2">
<button type="button" class="btn btn-success">Audio Call</button>
<button type="button" class="btn btn-primary">Video Call</button>
</div>
<div class="modal-footer">${modal_close_button}</div>
</div>
</div>
`;
}
22 changes: 22 additions & 0 deletions src/plugins/jingle/templates/toolbar-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { html } from 'lit';
import { __ } from "i18n";
import { JINGLE_CALL_STATUS } from '../constants';

export default (el) => {
const call_color = '--chat-toolbar-btn-color';
const end_call_color = '--chat-toolbar-btn-close-color';
const jingle_status = el.model.get('jingle_status');
let button_color, i18n_start_call;
if (jingle_status === JINGLE_CALL_STATUS.OUTGOING_PENDING || jingle_status === JINGLE_CALL_STATUS.ACTIVE) {
button_color = end_call_color;
i18n_start_call = __('Stop the call');
}
else {
button_color = call_color;
i18n_start_call = __('Start a call');
}
return html`
<button class="toggle-call" @click=${el.toggleJingleCallStatus} title="${i18n_start_call}">
<converse-icon id="temp" color="var(${ button_color })" class="fa fa-phone" size="1em"></converse-icon>
</button>`
}
Loading