Add videocall support to livechat

pull/4095/head
Diego Sampaio 9 years ago
parent 10019b9509
commit 66a67cda69
  1. 2
      packages/rocketchat-i18n/i18n/en.i18n.json
  2. 2
      packages/rocketchat-i18n/i18n/pt.i18n.json
  3. 80
      packages/rocketchat-livechat/app/client/lib/LivechatVideoCall.js
  4. 7
      packages/rocketchat-livechat/app/client/lib/_livechat.js
  5. 15
      packages/rocketchat-livechat/app/client/lib/_visitor.coffee
  6. 131
      packages/rocketchat-livechat/app/client/stylesheets/main.less
  7. 3
      packages/rocketchat-livechat/app/client/views/livechatWindow.html
  8. 7
      packages/rocketchat-livechat/app/client/views/livechatWindow.js
  9. 13
      packages/rocketchat-livechat/app/client/views/messages.html
  10. 26
      packages/rocketchat-livechat/app/client/views/messages.js
  11. 5
      packages/rocketchat-livechat/app/client/views/register.html
  12. 21
      packages/rocketchat-livechat/app/client/views/register.js
  13. 27
      packages/rocketchat-livechat/app/client/views/videoCall.html
  14. 36
      packages/rocketchat-livechat/app/client/views/videoCall.js
  15. 3
      packages/rocketchat-livechat/app/i18n/en.i18n.json
  16. 3
      packages/rocketchat-livechat/app/i18n/pt.i18n.json
  17. 9
      packages/rocketchat-livechat/config.js
  18. 2
      packages/rocketchat-livechat/server/lib/Livechat.js
  19. 4
      packages/rocketchat-livechat/server/methods/getInitialData.js
  20. 4
      packages/rocketchat-videobridge/client/public/external_api.js

@ -211,6 +211,7 @@
"Back_to_integrations" : "Back to integrations",
"Back_to_login" : "Back to login",
"Back_to_permissions" : "Back to permissions",
"Beta_feature_Depends_on_Video_Conference_to_be_enabled" : "Beta feature. Depends on Video Conference to be enabled.",
"Body" : "Body",
"bold" : "bold",
"bot_request" : "Bot request",
@ -1335,6 +1336,7 @@
"Verified" : "Verified",
"Version" : "Version",
"Videocall_declined" : "Videocall declined.",
"Videocall_enabled" : "Videocall enabled",
"Video_Chat_Window" : "Video Chat",
"View_All" : "View All",
"View_Logs" : "View Logs",

@ -203,6 +203,7 @@
"Back_to_integrations" : "Voltar para integrações",
"Back_to_login" : "Voltar para o login",
"Back_to_permissions" : "Voltar para permissões",
"Beta_feature_Depends_on_Video_Conference_to_be_enabled" : "Funcionalidade Beta! Depende que Vídeo Conferência esteja habilitado",
"Body" : "Corpo",
"bold" : "negrito",
"BotHelpers_userFields" : "Campos de usuário",
@ -1223,6 +1224,7 @@
"Verified" : "Verificado",
"Version" : "Versão",
"Videocall_declined" : "Chamada de vídeo negada.",
"Videocall_enabled" : "Vídeoconferência habilitada",
"Video_Chat_Window" : "Vídeo Chat",
"View_All" : "Ver Todos",
"View_Logs" : "Ver Logs",

@ -0,0 +1,80 @@
/* globals LivechatVideoCall, cordova, JitsiMeetExternalAPI */
LivechatVideoCall = new (class LivechatVideoCall {
constructor() {
this.live = new ReactiveVar(false);
this.calling = new ReactiveVar(false);
if (typeof JitsiMeetExternalAPI === 'undefined') {
$.getScript('/packages/rocketchat_videobridge/client/public/external_api.js');
}
}
askPermissions(callback) {
if (Meteor.isCordova) {
cordova.plugins.diagnostic.requestCameraAuthorization(() => {
cordova.plugins.diagnostic.requestMicrophoneAuthorization(() => {
callback(true);
}, (error) => {
console.error(error);
});
}, (error) => {
console.error(error);
});
} else {
return callback(true);
}
}
request() {
this.askPermissions((granted) => {
if (granted) {
this.calling.set(true);
Meteor.call('livechat:startVideoCall', visitor.getRoom(true), (error, result) => {
if (error) {
return;
}
visitor.subscribeToRoom(result.roomId);
// after get ok from server, start the chat
this.start(result.domain, result.jitsiRoom);
});
}
});
}
start(domain, room) {
Meteor.defer(() => {
let interfaceConfig = {};
interfaceConfig['TOOLBAR_BUTTONS'] = '[""]';
interfaceConfig['APP_NAME'] = '"Livechat"';
interfaceConfig['INITIAL_TOOLBAR_TIMEOUT'] = '5000';
interfaceConfig['MIN_WIDTH'] = '300';
interfaceConfig['FILM_STRIP_MAX_HEIGHT'] = '50';
this.api = new JitsiMeetExternalAPI(domain, room, $('.video-call').width(), $('.video-call').height(), $('.video-call .container').get(0), {}, interfaceConfig);
this.api.addEventListener('videoConferenceJoined', () => {
this.api.executeCommand('toggleFilmStrip', []);
});
this.live.set(true);
});
}
finish() {
this.live.set(false);
this.calling.set(false);
this.api.dispose();
}
isActive() {
return this.live.get() || this.calling.get();
}
isLive() {
return this.live.get();
}
});
/* exported LivechatVideoCall */

@ -16,6 +16,7 @@ this.Livechat = new (class Livechat {
this._offlineUnavailableMessage = new ReactiveVar('');
this._displayOfflineForm = new ReactiveVar(true);
this._offlineSuccessMessage = new ReactiveVar(TAPi18n.__('Thanks_We_ll_get_back_to_you_soon'));
this._videoCall = new ReactiveVar(false);
}
get online() {
@ -51,6 +52,9 @@ this.Livechat = new (class Livechat {
get offlineSuccessMessage() {
return this._offlineSuccessMessage.get();
}
get videoCall() {
return this._videoCall.get();
}
set online(value) {
this._online.set(value);
@ -93,4 +97,7 @@ this.Livechat = new (class Livechat {
set offlineFontColor(value) {
this._offlineFontColor.set(value);
}
set videoCall(value) {
this._videoCall.set(value);
}
})();

@ -35,14 +35,17 @@ msgStream = new Meteor.Streamer 'room-messages'
subscribeToRoom = (roomId) ->
msgStream.on roomId, (msg) ->
if msg.t is 'command'
if msg.msg is 'survey'
unless $('body #survey').length
Blaze.render(Template.survey, $('body').get(0))
else
switch msg.msg
when 'survey'
unless $('body #survey').length
Blaze.render(Template.survey, $('body').get(0))
when 'endCall'
LivechatVideoCall.finish()
else if msg.t isnt 'livechat_video_call'
ChatMessage.upsert { _id: msg._id }, msg
# notification sound
if Session.equals('sound', true)
# notification sound
if Session.equals('sound', true)
if msg.u._id isnt Meteor.user()._id
$('#chatAudioNotification')[0].play();

@ -388,39 +388,52 @@ input:focus {
border-left: 1px solid @window-border-color;
border-right: 1px solid @window-border-color;
.input-wrapper {
padding: 6px 6px 0 6px;
padding-right: 30px;
textarea {
display: block;
padding: 6px 8px;
padding-right: 38px;
overflow-y: auto;
resize: none;
margin: 0;
max-height: 200px;
width: 100%;
font-size: 12px;
-webkit-appearance: none;
height: 28px;
line-height: normal;
background-color: #fff;
position: relative;
.message-bar {
display: flex;
flex-direction: row;
padding-top: 6px;
.input-wrapper {
flex-grow: 1;
padding-left: 6px;
textarea {
display: block;
padding: 6px 8px;
padding-right: 38px;
overflow-y: auto;
resize: none;
margin: 0;
max-height: 200px;
width: 100%;
font-size: 12px;
-webkit-appearance: none;
height: 28px;
line-height: normal;
background-color: #fff;
position: relative;
}
}
}
.send-button {
float: right;
position: relative;
width: 15px;
height: 15px;
right: 10px;
top: -22px;
fill: @secondary-font-color;
cursor: pointer;
.transition(color .15s ease-out);
&:hover {
fill: @primary-font-color;
.buttons {
color: @secondary-font-color;
fill: @secondary-font-color;
display: flex;
align-items: center;
padding: 0 5px;
svg {
width: 15px;
height: 15px;
margin: 0 4px;
cursor: pointer;
.transition(fill .15s ease-out);
&:hover {
fill: @primary-font-color;
}
}
}
}
.toggle-options {
@ -657,6 +670,62 @@ input:focus {
border-top-left-radius: inherit;
}
.video-call {
position: fixed;
top: @header-min-height;
bottom: 0;
left: 0;
right: 0;
background-color: #000;
z-index: 11;
.video-overlay {
position: fixed;
top: @header-min-height;
bottom: 0;
left: 0;
right: 0;
z-index: 13;
.toolbar {
position: absolute;
bottom: 40px;
width: 100%;
text-align: center;
opacity: 0;
visibility: hidden;
.transform(translateY(50px));
.transition(opacity 0.175s ease-out, transform 0.175s ease-out, visibility 0.175s ease-out);
&.visible {
opacity: 1;
visibility: visible;
.transform(translateY(0px));
}
.end-call {
background-color: red;
fill: white;
border-radius: 50%;
height: 60px;
width: 60px;
text-align: center;
outline: none;
svg {
width: 30px;
height: 30px;
}
}
}
}
.container {
z-index: 12;
}
}
@media all and(max-height: 200px) {
.livechat-room {
.title {

@ -36,4 +36,7 @@
{{> poweredBy }}
</div>
{{/if}}
{{#if videoCalling}}
{{> videoCall}}
{{/if}}
</template>

@ -1,4 +1,5 @@
/* globals Department, Livechat */
/* globals Department, Livechat, LivechatVideoCall */
Template.livechatWindow.helpers({
title() {
return Livechat.title;
@ -37,6 +38,9 @@ Template.livechatWindow.helpers({
offlineUnavailableMessage: Livechat.offlineUnavailableMessage.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br>$2'),
displayOfflineForm: Livechat.displayOfflineForm
};
},
videoCalling() {
return LivechatVideoCall.isActive();
}
});
@ -92,6 +96,7 @@ Template.livechatWindow.onCreated(function() {
Livechat.onlineColor = result.color;
Livechat.online = true;
}
Livechat.videoCall = result.videoCall;
Livechat.registrationForm = result.registrationForm;
if (result.room) {

@ -16,10 +16,17 @@
</div>
</div>
<div class="footer">
<div class="input-wrapper">
<textarea class="input-message" placeholder="{{_ "Type_your_message"}}"></textarea>
<div class="message-bar">
<div class="input-wrapper">
<textarea class="input-message" placeholder="{{_ "Type_your_message"}}"></textarea>
</div>
<div class="buttons">
<svg class="send-button" aria-label="{{_ "Send"}}" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1764 11q33 24 27 64l-256 1536q-5 29-32 45-14 8-31 8-11 0-24-5l-453-185-242 295q-18 23-49 23-13 0-22-4-19-7-30.5-23.5t-11.5-36.5v-349l864-1059-1069 925-395-162q-37-14-40-55-2-40 32-59l1664-960q15-9 32-9 20 0 36 11z"/></svg>
{{#if videoCallEnabled}}
<svg class="video-button" aria-label="{{_ "Video"}}" viewBox="0 0 459 459" xmlns="http://www.w3.org/2000/svg"><path d="M357,191.25V102c0-15.3-10.2-25.5-25.5-25.5h-306C10.2,76.5,0,86.7,0,102v255c0,15.3,10.2,25.5,25.5,25.5h306 c15.3,0,25.5-10.2,25.5-25.5v-89.25l102,102V89.25L357,191.25z"/></svg>
{{/if}}
</div>
</div>
<svg class="send-button" aria-label="{{_ "Send"}}" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1764 11q33 24 27 64l-256 1536q-5 29-32 45-14 8-31 8-11 0-24-5l-453-185-242 295q-18 23-49 23-13 0-22-4-19-7-30.5-23.5t-11.5-36.5v-349l864-1059-1069 925-395-162q-37-14-40-55-2-40 32-59l1664-960q15-9 32-9 20 0 36 11z"/></svg>
{{> options show=showOptions}}
<button class="toggle-options">{{optionsLink}}</button>

@ -1,3 +1,5 @@
/* globals Livechat, LivechatVideoCall */
Template.messages.helpers({
messages() {
return ChatMessage.find({
@ -24,6 +26,9 @@ Template.messages.helpers({
} else {
return t('Options');
}
},
videoCallEnabled() {
return Livechat.videoCall;
}
});
@ -52,6 +57,27 @@ Template.messages.events({
},
'click .toggle-options': function(event, instance) {
instance.showOptions.set(!instance.showOptions.get());
},
'click .video-button': function(event) {
event.preventDefault();
if (!Meteor.userId()) {
Meteor.call('livechat:registerGuest', { token: visitor.getToken() }, (error, result) => {
if (error) {
return console.log(error.reason);
}
Meteor.loginWithToken(result.token, (error) => {
if (error) {
return console.log(error.reason);
}
LivechatVideoCall.request();
});
});
} else {
LivechatVideoCall.request();
}
}
});

@ -20,6 +20,11 @@
</select>
{{/if}}
<button type="submit" id="btnEntrar" class="button"> {{_ "Start_Chat"}} </button>
{{#if videoCallEnabled}}
<br>
<button type="submit" class="button request-video"> {{_ "Request_video_chat"}} </button>
{{/if}}
</form>
</div>
</template>

@ -1,3 +1,5 @@
/* globals Department, Livechat, LivechatVideoCall */
Template.register.helpers({
error() {
return Template.instance().error.get();
@ -10,6 +12,9 @@ Template.register.helpers({
},
departments() {
return Department.find();
},
videoCallEnabled() {
return Livechat.videoCall;
}
});
@ -17,6 +22,14 @@ Template.register.events({
'submit #livechat-registration'(e, instance) {
var $email, $name;
e.preventDefault();
let start = () => {
instance.hideError();
if (instance.request === 'video') {
LivechatVideoCall.request();
}
};
$name = instance.$('input[name=name]');
$email = instance.$('input[name=email]');
if (!($name.val().trim() && $email.val().trim())) {
@ -44,17 +57,25 @@ Template.register.events({
if (error) {
return instance.showError(error.reason);
}
start();
});
});
}
},
'click .error'(e, instance) {
return instance.hideError();
},
'click .request-chat'(e, instance) {
instance.request = 'chat';
},
'click .request-video'(e, instance) {
instance.request = 'video';
}
});
Template.register.onCreated(function() {
this.error = new ReactiveVar();
this.request = '';
this.showError = (msg) => {
$('.error').addClass('show');
this.error.set(msg);

@ -0,0 +1,27 @@
<template name="videoCall">
<div class="video-call">
<div class="video-overlay">
<div class="toolbar {{visible}}">
<button class="end-call">
<svg viewBox="0 0 578 578" xmlns="http://www.w3.org/2000/svg">
<path d="M577.83,456.128c1.225,9.385-1.635,17.545-8.568,24.48l-81.396,80.781
c-3.672,4.08-8.465,7.551-14.381,10.404c-5.916,2.857-11.729,4.693-17.439,5.508c-0.408,0-1.635,0.105-3.676,0.309
c-2.037,0.203-4.689,0.307-7.953,0.307c-7.754,0-20.301-1.326-37.641-3.979s-38.555-9.182-63.645-19.584
c-25.096-10.404-53.553-26.012-85.376-46.818c-31.823-20.805-65.688-49.367-101.592-85.68
c-28.56-28.152-52.224-55.08-70.992-80.783c-18.768-25.705-33.864-49.471-45.288-71.299
c-11.425-21.828-19.993-41.616-25.705-59.364S4.59,177.362,2.55,164.51s-2.856-22.95-2.448-30.294
c0.408-7.344,0.612-11.424,0.612-12.24c0.816-5.712,2.652-11.526,5.508-17.442s6.324-10.71,10.404-14.382L98.022,8.756
c5.712-5.712,12.24-8.568,19.584-8.568c5.304,0,9.996,1.53,14.076,4.59s7.548,6.834,10.404,11.322l65.484,124.236
c3.672,6.528,4.692,13.668,3.06,21.42c-1.632,7.752-5.1,14.28-10.404,19.584l-29.988,29.988c-0.816,0.816-1.53,2.142-2.142,3.978
s-0.918,3.366-0.918,4.59c1.632,8.568,5.304,18.36,11.016,29.376c4.896,9.792,12.444,21.726,22.644,35.802
s24.684,30.293,43.452,48.653c18.36,18.77,34.68,33.354,48.96,43.76c14.277,10.4,26.215,18.053,35.803,22.949
c9.588,4.896,16.932,7.854,22.031,8.871l7.648,1.531c0.816,0,2.145-0.307,3.979-0.918c1.836-0.613,3.162-1.326,3.979-2.143
l34.883-35.496c7.348-6.527,15.912-9.791,25.705-9.791c6.938,0,12.443,1.223,16.523,3.672h0.611l118.115,69.768
C571.098,441.238,576.197,447.968,577.83,456.128z"/>
</svg>
</button>
</div>
</div>
<div class="container"></div>
</div>
</template>

@ -0,0 +1,36 @@
/* globals LivechatVideoCall */
Template.videoCall.helpers({
visible() {
if (Template.instance().showToolbar.get()) {
return 'visible';
}
}
});
Template.videoCall.events({
'click .end-call'() {
LivechatVideoCall.finish();
},
'click .video-overlay'(e, instance) {
if (instance.timeout) {
clearTimeout(instance.timeout);
}
instance.showToolbar.set(!instance.showToolbar.get());
if (instance.showToolbar.get()) {
instance.timeout = setTimeout(() => {
instance.showToolbar.set(false);
}, 3000);
}
}
});
Template.videoCall.onCreated(function() {
this.timeout = null;
this.showToolbar = new ReactiveVar(true);
this.timeout = setTimeout(() => {
this.showToolbar.set(false);
}, 10000);
});

@ -18,6 +18,7 @@
"Please_answer_survey" : "Please take a moment to answer a quick survey about this chat",
"Please_fill_name_and_email" : "Please fill name and email",
"Powered_by" : "Powered by",
"Request_video_chat" : "Request video chat",
"Select_a_department" : "Select a department",
"Send" : "Send",
"Skip" : "Skip",
@ -35,4 +36,4 @@
"Yes" : "Yes",
"You" : "You",
"You_must_complete_all_fields" : "You must complete all fields"
}
}

@ -18,6 +18,7 @@
"Please_answer_survey" : "Por favor nos dê um momento para responder uma rápida pesquisa sobre este chat",
"Please_fill_name_and_email" : "Por favor preencha nome e email",
"Powered_by" : "Distribuído por",
"Request_video_chat" : "Solicitar vídeoconferência",
"Select_a_department" : "Selecione um departamento",
"Send" : "Enviar",
"Skip" : "Pular",
@ -35,4 +36,4 @@
"Yes" : "Sim",
"You" : "Você",
"You_must_complete_all_fields" : "Você deve preencher todos os campos"
}
}

@ -186,4 +186,13 @@ Meteor.startup(function() {
public: true,
i18nLabel: 'Office_Hours_Enabled'
});
RocketChat.settings.add('Livechat_videocall_enabled', false, {
type: 'boolean',
group: 'Livechat',
public: true,
i18nLabel: 'Videocall_enabled',
i18nDescription: 'Beta_feature_Depends_on_Video_Conference_to_be_enabled',
enableQuery: { _id: 'Jitsi_Enabled', value: true }
});
});

@ -217,6 +217,8 @@ RocketChat.Livechat = {
'Livechat_offline_success_message',
'Livechat_offline_form_unavailable',
'Livechat_display_offline_form',
'Livechat_videocall_enabled',
'Jitsi_Enabled',
'Language'
]).forEach((setting) => {
settings[setting._id] = setting.value;

@ -13,7 +13,8 @@ Meteor.methods({
offlineMessage: null,
offlineSuccessMessage: null,
offlineUnavailableMessage: null,
displayOfflineForm: null
displayOfflineForm: null,
videoCall: null
};
const room = RocketChat.models.Rooms.findOpenByVisitorToken(visitorToken, {
@ -44,6 +45,7 @@ Meteor.methods({
info.offlineUnavailableMessage = initSettings.Livechat_offline_form_unavailable;
info.displayOfflineForm = initSettings.Livechat_display_offline_form;
info.language = initSettings.Language;
info.videoCall = initSettings.Livechat_videocall_enabled === true && initSettings.Jitsi_Enabled === true;
RocketChat.models.LivechatTrigger.find().forEach((trigger) => {
info.triggers.push(trigger);

@ -8,8 +8,8 @@
* The minimum width for the Jitsi Meet frame
* @type {number}
*/
// var MIN_WIDTH = 200;
var MIN_WIDTH = 790;
var MIN_WIDTH = 200;
// var MIN_WIDTH = 790;
/**
* The minimum height for the Jitsi Meet frame

Loading…
Cancel
Save