Merge branch 'converse-omemo'

This commit is contained in:
JC Brand 2018-08-27 21:14:03 +02:00
commit 11cc41d3a3
57 changed files with 16919 additions and 11089 deletions

View File

@ -9,12 +9,13 @@
"plugins": ["lodash"],
"extends": ["eslint:recommended", "plugin:lodash/canonical"],
"globals": {
"Uint8Array": true,
"Promise": true,
"converse": true,
"window": true,
"sinon": true,
"define": true,
"require": true
"require": true,
"sinon": true,
"window": true
},
"rules": {
"lodash/prefer-lodash-method": [2, {

1
.gitignore vendored
View File

@ -12,6 +12,7 @@
.idea
.su?
builds/*
3rdparty/libsignal-protocol-javascript/
*.map
dist/converse-no-dependencies-es2015.js

3295
3rdparty/bytebuffer.js vendored Normal file

File diff suppressed because it is too large Load Diff

1207
3rdparty/long.js vendored Normal file

File diff suppressed because it is too large Load Diff

5222
3rdparty/protobuf.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2346,7 +2346,7 @@
--primary: #387592;
--secondary: #6c757d;
--success: #3AA569;
--info: #17a2b8;
--info: #3AA569;
--warning: #ffc107;
--danger: #E77051;
--light: #f8f9fa;
@ -3594,24 +3594,24 @@
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-info {
color: #fff;
background-color: #17a2b8;
border-color: #17a2b8; }
background-color: #3AA569;
border-color: #3AA569; }
#conversejs .btn-info:hover {
color: #fff;
background-color: #138496;
border-color: #117a8b; }
background-color: #308957;
border-color: #2d7f51; }
#conversejs .btn-info:focus, #conversejs .btn-info.focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-info.disabled, #conversejs .btn-info:disabled {
color: #fff;
background-color: #17a2b8;
border-color: #17a2b8; }
background-color: #3AA569;
border-color: #3AA569; }
#conversejs .btn-info:not(:disabled):not(.disabled):active, #conversejs .btn-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-info.dropdown-toggle {
color: #fff;
background-color: #117a8b;
border-color: #10707f; }
background-color: #2d7f51;
border-color: #29764b; }
#conversejs .btn-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-info.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-warning {
color: #212529;
background-color: #ffc107;
@ -3753,25 +3753,25 @@
#conversejs .btn-outline-success:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-success.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-info {
color: #17a2b8;
color: #3AA569;
background-color: transparent;
background-image: none;
border-color: #17a2b8; }
border-color: #3AA569; }
#conversejs .btn-outline-info:hover {
color: #fff;
background-color: #17a2b8;
border-color: #17a2b8; }
background-color: #3AA569;
border-color: #3AA569; }
#conversejs .btn-outline-info:focus, #conversejs .btn-outline-info.focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-info.disabled, #conversejs .btn-outline-info:disabled {
color: #17a2b8;
color: #3AA569;
background-color: transparent; }
#conversejs .btn-outline-info:not(:disabled):not(.disabled):active, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-outline-info.dropdown-toggle {
color: #fff;
background-color: #17a2b8;
border-color: #17a2b8; }
background-color: #3AA569;
border-color: #3AA569; }
#conversejs .btn-outline-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-info.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); }
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-warning {
color: #ffc107;
background-color: transparent;
@ -4371,6 +4371,321 @@
background-color: #e9ecef;
border-left: 1px solid #ced4da;
border-radius: 0 0.25rem 0.25rem 0; }
#conversejs .nav {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none; }
#conversejs .nav-link {
display: block;
padding: 0.5rem 1rem; }
#conversejs .nav-link:hover, #conversejs .nav-link:focus {
text-decoration: none; }
#conversejs .nav-link.disabled {
color: #6c757d; }
#conversejs .nav-tabs {
border-bottom: 1px solid #dee2e6; }
#conversejs .nav-tabs .nav-item {
margin-bottom: -1px; }
#conversejs .nav-tabs .nav-link {
border: 1px solid transparent;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem; }
#conversejs .nav-tabs .nav-link:hover, #conversejs .nav-tabs .nav-link:focus {
border-color: #e9ecef #e9ecef #dee2e6; }
#conversejs .nav-tabs .nav-link.disabled {
color: #6c757d;
background-color: transparent;
border-color: transparent; }
#conversejs .nav-tabs .nav-link.active,
#conversejs .nav-tabs .nav-item.show .nav-link {
color: #495057;
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff; }
#conversejs .nav-tabs .dropdown-menu {
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0; }
#conversejs .nav-pills .nav-link {
border-radius: 0.25rem; }
#conversejs .nav-pills .nav-link.active,
#conversejs .nav-pills .show > .nav-link {
color: #fff;
background-color: #387592; }
#conversejs .nav-fill .nav-item {
flex: 1 1 auto;
text-align: center; }
#conversejs .nav-justified .nav-item {
flex-basis: 0;
flex-grow: 1;
text-align: center; }
#conversejs .tab-content > .tab-pane {
display: none; }
#conversejs .tab-content > .active {
display: block; }
#conversejs .navbar {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem; }
#conversejs .navbar > .container,
#conversejs .navbar > .container-fluid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between; }
#conversejs .navbar-brand {
display: inline-block;
padding-top: 0.3125rem;
padding-bottom: 0.3125rem;
margin-right: 1rem;
font-size: 1.25rem;
line-height: inherit;
white-space: nowrap; }
#conversejs .navbar-brand:hover, #conversejs .navbar-brand:focus {
text-decoration: none; }
#conversejs .navbar-nav {
display: flex;
flex-direction: column;
padding-left: 0;
margin-bottom: 0;
list-style: none; }
#conversejs .navbar-nav .nav-link {
padding-right: 0;
padding-left: 0; }
#conversejs .navbar-nav .dropdown-menu {
position: static;
float: none; }
#conversejs .navbar-text {
display: inline-block;
padding-top: 0.5rem;
padding-bottom: 0.5rem; }
#conversejs .navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
align-items: center; }
#conversejs .navbar-toggler {
padding: 0.25rem 0.75rem;
font-size: 1.25rem;
line-height: 1;
background-color: transparent;
border: 1px solid transparent;
border-radius: 0.25rem; }
#conversejs .navbar-toggler:hover, #conversejs .navbar-toggler:focus {
text-decoration: none; }
#conversejs .navbar-toggler:not(:disabled):not(.disabled) {
cursor: pointer; }
#conversejs .navbar-toggler-icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
vertical-align: middle;
content: "";
background: no-repeat center center;
background-size: 100% 100%; }
@media (max-width: 575.98px) {
#conversejs .navbar-expand-sm > .container,
#conversejs .navbar-expand-sm > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 576px) {
#conversejs .navbar-expand-sm {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-sm .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-sm .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-sm .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-sm .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-sm > .container,
#conversejs .navbar-expand-sm > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-sm .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-sm .navbar-toggler {
display: none; }
#conversejs .navbar-expand-sm .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 767.98px) {
#conversejs .navbar-expand-md > .container,
#conversejs .navbar-expand-md > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 768px) {
#conversejs .navbar-expand-md {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-md .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-md .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-md .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-md .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-md > .container,
#conversejs .navbar-expand-md > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-md .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-md .navbar-toggler {
display: none; }
#conversejs .navbar-expand-md .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 991.98px) {
#conversejs .navbar-expand-lg > .container,
#conversejs .navbar-expand-lg > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 992px) {
#conversejs .navbar-expand-lg {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-lg .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-lg .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-lg .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-lg .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-lg > .container,
#conversejs .navbar-expand-lg > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-lg .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-lg .navbar-toggler {
display: none; }
#conversejs .navbar-expand-lg .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
@media (max-width: 1199.98px) {
#conversejs .navbar-expand-xl > .container,
#conversejs .navbar-expand-xl > .container-fluid {
padding-right: 0;
padding-left: 0; } }
@media (min-width: 1200px) {
#conversejs .navbar-expand-xl {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand-xl .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand-xl .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand-xl .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand-xl .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand-xl > .container,
#conversejs .navbar-expand-xl > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand-xl .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand-xl .navbar-toggler {
display: none; }
#conversejs .navbar-expand-xl .dropup .dropdown-menu {
top: auto;
bottom: 100%; } }
#conversejs .navbar-expand {
flex-flow: row nowrap;
justify-content: flex-start; }
#conversejs .navbar-expand > .container,
#conversejs .navbar-expand > .container-fluid {
padding-right: 0;
padding-left: 0; }
#conversejs .navbar-expand .navbar-nav {
flex-direction: row; }
#conversejs .navbar-expand .navbar-nav .dropdown-menu {
position: absolute; }
#conversejs .navbar-expand .navbar-nav .dropdown-menu-right {
right: 0;
left: auto; }
#conversejs .navbar-expand .navbar-nav .nav-link {
padding-right: 0.5rem;
padding-left: 0.5rem; }
#conversejs .navbar-expand > .container,
#conversejs .navbar-expand > .container-fluid {
flex-wrap: nowrap; }
#conversejs .navbar-expand .navbar-collapse {
display: flex !important;
flex-basis: auto; }
#conversejs .navbar-expand .navbar-toggler {
display: none; }
#conversejs .navbar-expand .dropup .dropdown-menu {
top: auto;
bottom: 100%; }
#conversejs .navbar-light .navbar-brand {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-brand:hover, #conversejs .navbar-light .navbar-brand:focus {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-nav .nav-link {
color: rgba(0, 0, 0, 0.5); }
#conversejs .navbar-light .navbar-nav .nav-link:hover, #conversejs .navbar-light .navbar-nav .nav-link:focus {
color: rgba(0, 0, 0, 0.7); }
#conversejs .navbar-light .navbar-nav .nav-link.disabled {
color: rgba(0, 0, 0, 0.3); }
#conversejs .navbar-light .navbar-nav .show > .nav-link,
#conversejs .navbar-light .navbar-nav .active > .nav-link,
#conversejs .navbar-light .navbar-nav .nav-link.show,
#conversejs .navbar-light .navbar-nav .nav-link.active {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-toggler {
color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 0, 0, 0.1); }
#conversejs .navbar-light .navbar-toggler-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
#conversejs .navbar-light .navbar-text {
color: rgba(0, 0, 0, 0.5); }
#conversejs .navbar-light .navbar-text a {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-light .navbar-text a:hover, #conversejs .navbar-light .navbar-text a:focus {
color: rgba(0, 0, 0, 0.9); }
#conversejs .navbar-dark .navbar-brand {
color: #fff; }
#conversejs .navbar-dark .navbar-brand:hover, #conversejs .navbar-dark .navbar-brand:focus {
color: #fff; }
#conversejs .navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, 0.5); }
#conversejs .navbar-dark .navbar-nav .nav-link:hover, #conversejs .navbar-dark .navbar-nav .nav-link:focus {
color: rgba(255, 255, 255, 0.75); }
#conversejs .navbar-dark .navbar-nav .nav-link.disabled {
color: rgba(255, 255, 255, 0.25); }
#conversejs .navbar-dark .navbar-nav .show > .nav-link,
#conversejs .navbar-dark .navbar-nav .active > .nav-link,
#conversejs .navbar-dark .navbar-nav .nav-link.show,
#conversejs .navbar-dark .navbar-nav .nav-link.active {
color: #fff; }
#conversejs .navbar-dark .navbar-toggler {
color: rgba(255, 255, 255, 0.5);
border-color: rgba(255, 255, 255, 0.1); }
#conversejs .navbar-dark .navbar-toggler-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }
#conversejs .navbar-dark .navbar-text {
color: rgba(255, 255, 255, 0.5); }
#conversejs .navbar-dark .navbar-text a {
color: #fff; }
#conversejs .navbar-dark .navbar-text a:hover, #conversejs .navbar-dark .navbar-text a:focus {
color: #fff; }
#conversejs .card {
position: relative;
display: flex;
@ -4582,11 +4897,11 @@
background-color: #2d7f51; }
#conversejs .badge-info {
color: #fff;
background-color: #17a2b8; }
background-color: #3AA569; }
#conversejs .badge-info[href]:hover, #conversejs .badge-info[href]:focus {
color: #fff;
text-decoration: none;
background-color: #117a8b; }
background-color: #2d7f51; }
#conversejs .badge-warning {
color: #212529;
background-color: #ffc107; }
@ -4658,13 +4973,13 @@
#conversejs .alert-success .alert-link {
color: #11301f; }
#conversejs .alert-info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb; }
color: #1e5637;
background-color: #d8ede1;
border-color: #c8e6d5; }
#conversejs .alert-info hr {
border-top-color: #abdde5; }
border-top-color: #b6dec8; }
#conversejs .alert-info .alert-link {
color: #062c33; }
color: #11301f; }
#conversejs .alert-warning {
color: #856404;
background-color: #fff3cd;
@ -4782,15 +5097,15 @@
background-color: #1e5637;
border-color: #1e5637; }
#conversejs .list-group-item-info {
color: #0c5460;
background-color: #bee5eb; }
color: #1e5637;
background-color: #c8e6d5; }
#conversejs .list-group-item-info.list-group-item-action:hover, #conversejs .list-group-item-info.list-group-item-action:focus {
color: #0c5460;
background-color: #abdde5; }
color: #1e5637;
background-color: #b6dec8; }
#conversejs .list-group-item-info.list-group-item-action.active {
color: #fff;
background-color: #0c5460;
border-color: #0c5460; }
background-color: #1e5637;
border-color: #1e5637; }
#conversejs .list-group-item-warning {
color: #856404;
background-color: #ffeeba; }
@ -5179,11 +5494,11 @@
#conversejs button.bg-success:focus {
background-color: #2d7f51 !important; }
#conversejs .bg-info {
background-color: #17a2b8 !important; }
background-color: #3AA569 !important; }
#conversejs a.bg-info:hover, #conversejs a.bg-info:focus,
#conversejs button.bg-info:hover,
#conversejs button.bg-info:focus {
background-color: #117a8b !important; }
background-color: #2d7f51 !important; }
#conversejs .bg-warning {
background-color: #ffc107 !important; }
#conversejs a.bg-warning:hover, #conversejs a.bg-warning:focus,
@ -5239,7 +5554,7 @@
#conversejs .border-success {
border-color: #3AA569 !important; }
#conversejs .border-info {
border-color: #17a2b8 !important; }
border-color: #3AA569 !important; }
#conversejs .border-warning {
border-color: #ffc107 !important; }
#conversejs .border-danger {
@ -6792,9 +7107,9 @@
#conversejs a.text-success:hover, #conversejs a.text-success:focus {
color: #2d7f51 !important; }
#conversejs .text-info {
color: #17a2b8 !important; }
color: #3AA569 !important; }
#conversejs a.text-info:hover, #conversejs a.text-info:focus {
color: #117a8b !important; }
color: #2d7f51 !important; }
#conversejs .text-warning {
color: #ffc107 !important; }
#conversejs a.text-warning:hover, #conversejs a.text-warning:focus {
@ -6868,6 +7183,8 @@ body.reset {
font-size: 14px;
direction: ltr;
z-index: 1031; }
#conversejs .nopadding {
padding: 0 !important; }
#conversejs.converse-overlayed > .row {
flex-direction: row-reverse; }
#conversejs.converse-fullscreen .converse-chatboxes, #conversejs.converse-mobile .converse-chatboxes {
@ -7213,8 +7530,9 @@ body.reset {
@media screen and (max-height: 450px) {
#conversejs {
left: 0; } }
#conversejs form .form-group {
margin-bottom: 2em; }
#conversejs .btn--small {
font-size: 80%;
font-weight: normal; }
#conversejs form .form-check-label {
margin-top: 0.3rem; }
#conversejs form .form-control::-webkit-input-placeholder {
@ -7286,12 +7604,11 @@ body.reset {
color: #79a5ba; }
#conversejs form.converse-form .text-muted.error {
color: #A53214; }
#conversejs form.converse-form--modal {
padding-bottom: 0; }
#conversejs form.converse-centered-form {
text-align: center; }
#conversejs #user-profile-modal label {
font-weight: bold; }
#conversejs .chatbox-navback {
display: none; }
#conversejs .flyout {
@ -7337,6 +7654,13 @@ body.reset {
font-size: 80%; }
#conversejs .chat-head .chatbox-buttons {
flex-direction: row-reverse;
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
flex: 0 0 25%;
max-width: 25%;
padding: 0; }
#conversejs .chat-head .user-custom-message {
color: #e7f7ee;
@ -7843,10 +8167,6 @@ body.reset {
padding: 0.3em 0;
clear: left;
width: 100%; }
#conversejs #converse-modals .set-xmpp-status {
margin: 1em; }
#conversejs #converse-modals .set-xmpp-status .custom-control-label {
margin-top: 0.25em; }
#conversejs #controlbox {
margin-right: 1.5em; }
#conversejs #controlbox .box-flyout {
@ -8247,6 +8567,35 @@ body.reset {
#conversejs.converse-overlayed .converse-chatboxes .chatbox .box-flyout {
margin-left: 30px; } }
#conversejs #converse-modals .set-xmpp-status {
margin: 1em; }
#conversejs #converse-modals .set-xmpp-status .custom-control-label {
margin-top: 0.25em; }
#conversejs #converse-modals #omemo-tabpanel {
margin-top: 1em; }
#conversejs #converse-modals .btn {
font-weight: normal; }
#conversejs #converse-modals #user-profile-modal .profile-form label {
font-weight: bold; }
#conversejs #converse-modals #user-profile-modal .fingerprint-removal label {
display: flex;
padding: 0.75rem 1.25rem; }
#conversejs #converse-modals #user-profile-modal .list-group-item {
display: flex;
justify-content: left;
font-size: 95%; }
#conversejs #converse-modals #user-profile-modal .list-group-item input[type="checkbox"] {
margin-right: 1em; }
#conversejs #converse-modals .fingerprints {
width: 100%;
margin-bottom: 1em; }
#conversejs #converse-modals .fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%; }
#conversejs #converse-modals .fingerprint-trust .fingerprint {
margin-left: 1em; }
#conversejs #converse-roster {
text-align: left;
width: 100%;

View File

@ -11,6 +11,7 @@
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
<link type="text/css" rel="stylesheet" media="screen" href="css/fullpage.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="3rdparty/libsignal-protocol-javascript/dist/libsignal-protocol.js"></script>
<script src="dist/converse.js"></script>
</head>

8042
dist/converse.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: Converse.js 0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-22 11:17+0200\n"
"PO-Revision-Date: 2018-07-22 12:12+0200\n"
"PO-Revision-Date: 2018-07-22 15:37+0200\n"
"Last-Translator: JC Brand <jc@opkode.com>\n"
"Language-Team: Afrikaans <https://hosted.weblate.org/projects/conversejs/"
"translations/af/>\n"

View File

@ -134,12 +134,33 @@
<span class="chat-msg__heading">
<span class="chat-msg__author">Juliet Capulet</span>
<span class="chat-msg__time">15:31</span>
<span class="fa fa-lock"></span>
</span>
<div class="chat-msg__body">
<div class="chat-msg__message">
<span class="chat-msg__text">
O Romeo, Romeo! wherefore art thou Romeo?
Deny thy father and refuse thy name;
</span>
</div>
</div>
</div>
<div class="chat-msg__actions">
<button class="chat-msg__action fa fa-pencil" title="Edit this message">&nbsp;</button>
</div>
</div>
<div class="message chat-msg chat-msg--followup">
<canvas height="36" width="36" class="avatar chat-msg__avatar"></canvas>
<div class="chat-msg__content">
<span class="chat-msg__heading">
<span class="chat-msg__author">Juliet Capulet</span>
<span class="chat-msg__time">15:31</span>
<span class="fa fa-lock"></span>
</span>
<div class="chat-msg__body">
<div class="chat-msg__message">
<span class="chat-msg__text">
Or, if thou wilt not, be but sworn my love,
And I'll no longer be a Capulet.
</span>

5764
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,7 @@
"bootstrap": "^4.0.0",
"bootstrap.native": "^2.0.23",
"bourbon": "^4.3.2",
"bytebuffer": "^3.5.5",
"clean-css-cli": "^4.0.10",
"emojione": "^3.0.3",
"es6-promise": "^4.1.0",
@ -65,6 +66,7 @@
"jshint": "^2.9.4",
"lodash": "4.17.4",
"lodash-template-loader": "^2.0.0",
"long": "^3.1.0",
"lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader",
"minimist": "^1.2.0",
"moment": "~> 2.19.3 ",
@ -72,6 +74,7 @@
"otr": "0.2.16",
"pluggable.js": "2.0.0",
"po2json": "^0.4.4",
"protobufjs": "5.0.1",
"requirejs": "2.3.5",
"run-headless-chromium": "^0.1.1",
"sinon": "^2.1.0",

View File

@ -61,6 +61,8 @@
}
.chatbox-buttons {
flex-direction: row-reverse;
@include make-col-ready();
@include make-col(3);
padding: 0;
}

View File

@ -60,15 +60,6 @@
width: 100%;
}
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
}
#controlbox {
.box-flyout {
background-color: white;

View File

@ -61,6 +61,10 @@ body.reset {
direction: ltr;
z-index: 1031; // One more than bootstrap navbar
.nopadding {
padding: 0 !important;
}
&.converse-overlayed {
> .row {
flex-direction: row-reverse;

View File

@ -1,9 +1,10 @@
#conversejs {
form {
.form-group {
margin-bottom: 2em;
}
.btn--small {
font-size: 80%;
font-weight: normal;
}
form {
.form-check-label {
margin-top: $form-check-input-margin-y;
}
@ -103,6 +104,11 @@
}
}
}
&.converse-form--modal {
padding-bottom: 0;
}
&.converse-centered-form {
text-align: center;
}

56
sass/_modal.scss Normal file
View File

@ -0,0 +1,56 @@
#conversejs {
#converse-modals {
.set-xmpp-status {
margin: 1em;
.custom-control-label {
margin-top: 0.25em;
}
}
#omemo-tabpanel {
margin-top: 1em;
}
.btn {
font-weight: normal;
}
#user-profile-modal {
.profile-form {
label {
font-weight: bold;
}
}
.fingerprint-removal {
label {
display: flex;
padding: 0.75rem 1.25rem;
}
}
.list-group-item {
display: flex;
justify-content: left;
font-size: 95%;
input[type="checkbox"] {
margin-right: 1em;
}
}
}
.fingerprints {
width: 100%;
margin-bottom: 1em;
}
.fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%;
.fingerprint {
margin-left: 1em;
}
}
}
}

View File

@ -1,7 +0,0 @@
#conversejs {
#user-profile-modal {
label {
font-weight: bold;
}
}
}

View File

@ -34,6 +34,8 @@ $green: #3AA569;
$dark-green: #1E9652;
$darkest-green: #0E763B;
$info: $green !default;
$lightest-green: #E7FBF0;
$light-green: #5CBC86;
$green: #3AA569;

View File

@ -26,6 +26,8 @@
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/input-group";
@import "bootstrap/scss/custom-forms";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/badge";
@ -40,9 +42,9 @@
}
@import "core";
@import "forms";
@import "profile";
@import "chatbox";
@import "controlbox";
@import "modal";
@import "roster";
@import "lists";
@import "chatrooms";

View File

@ -314,52 +314,54 @@
[{'category': 'pubsub', 'type': 'pep'}],
['http://jabber.org/protocol/pubsub#publish-options']
).then(function () {
test_utils.waitUntil(function () {
return _converse.bookmarks;
}, 300).then(function () {
/* The stored data is automatically pushed to all of the user's
* connected resources.
*
* Publisher receives event notification
* -------------------------------------
* <message from='juliet@capulet.lit'
* to='juliet@capulet.lit/balcony'
* type='headline'
* id='rnfoo1'>
* <event xmlns='http://jabber.org/protocol/pubsub#event'>
* <items node='storage:bookmarks'>
* <item id='current'>
* <storage xmlns='storage:bookmarks'>
* <conference name='The Play&apos;s the Thing'
* autojoin='true'
* jid='theplay@conference.shakespeare.lit'>
* <nick>JC</nick>
* </conference>
* </storage>
* </item>
* </items>
* </event>
* </message>
*/
var stanza = $msg({
'from': 'dummy@localhost',
'to': 'dummy@localhost/resource',
'type': 'headline',
'id': 'rnfoo1'
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {'name': 'The Play&apos;s the Thing',
'autojoin': 'true',
'jid':'theplay@conference.shakespeare.lit'})
.c('nick').t('JC');
return test_utils.waitUntil(() => _converse.bookmarks);
}).then(function () {
// Emit here instead of mocking fetching of bookmarks.
_converse.emit('bookmarksInitialized');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.bookmarks.length).toBe(1);
expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
done();
});
/* The stored data is automatically pushed to all of the user's
* connected resources.
*
* Publisher receives event notification
* -------------------------------------
* <message from='juliet@capulet.lit'
* to='juliet@capulet.lit/balcony'
* type='headline'
* id='rnfoo1'>
* <event xmlns='http://jabber.org/protocol/pubsub#event'>
* <items node='storage:bookmarks'>
* <item id='current'>
* <storage xmlns='storage:bookmarks'>
* <conference name='The Play&apos;s the Thing'
* autojoin='true'
* jid='theplay@conference.shakespeare.lit'>
* <nick>JC</nick>
* </conference>
* </storage>
* </item>
* </items>
* </event>
* </message>
*/
var stanza = $msg({
'from': 'dummy@localhost',
'to': 'dummy@localhost/resource',
'type': 'headline',
'id': 'rnfoo1'
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'})
.c('storage', {'xmlns': 'storage:bookmarks'})
.c('conference', {'name': 'The Play&apos;s the Thing',
'autojoin': 'true',
'jid':'theplay@conference.shakespeare.lit'})
.c('nick').t('JC');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.bookmarks.length);
}).then(function () {
expect(_converse.bookmarks.length).toBe(1);
expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
done();
});
}));

View File

@ -27,10 +27,11 @@
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
let view;
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
test_utils.waitUntil(() => _converse.chatboxes.length == 2).then(() => {
var view = _converse.chatboxviews.get(contact_jid);
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => {
view = _converse.chatboxviews.get(contact_jid);
test_utils.sendMessage(view, '/help');
const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info:not(.chat-date)'), 0);
@ -39,13 +40,15 @@
expect(info_messages.pop().textContent).toBe('/me: Write in the third person');
expect(info_messages.pop().textContent).toBe('/clear: Remove messages');
var msg = $msg({
const msg = $msg({
from: contact_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('body').t('hello world').tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length);
}).then(() => {
expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1);
done();
});
@ -618,10 +621,10 @@
expect(view.model.get('chat_state')).toBe('inactive');
spyOn(_converse.connection, 'send');
view.model.maximize();
return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 700);
return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
}).then(() => {
expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
const calls = _.filter(_converse.connection.send.calls.all(), function (call) {
return call.args[0] instanceof Strophe.Builder;
});
expect(calls.length).toBe(1);
@ -632,7 +635,7 @@
expect($stanza.children().get(1).tagName).toBe('no-store');
expect($stanza.children().get(2).tagName).toBe('no-permanent-store');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
@ -741,7 +744,7 @@
spyOn(_converse, 'log');
recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, recipient_jid);
}).then(() => {
}).then((view) => {
var msg = $msg({
'from': _converse.bare_jid,
'id': (new Date()).getTime(),
@ -757,7 +760,8 @@
'type': 'chat'
}).c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
// Check that the chatbox and its view now exist
var chatbox = _converse.chatboxes.get(recipient_jid);
var chatboxview = _converse.chatboxviews.get(recipient_jid);
@ -886,7 +890,7 @@
spyOn(_converse, 'log');
recipient_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.openChatBoxFor(_converse, recipient_jid);
}).then(() => {
}).then((view) => {
var msg = $msg({
'from': _converse.bare_jid,
'id': (new Date()).getTime(),
@ -902,7 +906,8 @@
'type': 'chat'
}).c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
// Check that the chatbox and its view now exist
var chatbox = _converse.chatboxes.get(recipient_jid);
var chatboxview = _converse.chatboxviews.get(recipient_jid);
@ -936,11 +941,11 @@
let view;
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openControlBox();
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 1000)
.then(() => test_utils.openChatBoxFor(_converse, contact_jid))
.then(() => {
view = _converse.chatboxviews.get(contact_jid);
return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 500);
return test_utils.waitUntil(() => view.model.get('chat_state') === 'active', 1000);
}).then(() => {
console.log('chat_state set to active');
expect(view.model.get('chat_state')).toBe('active');
@ -954,10 +959,9 @@
view = _converse.chatboxviews.get(contact_jid);
expect(view.model.get('chat_state')).toBe('composing');
spyOn(_converse.connection, 'send');
return test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 500);
}).then(() => {
return test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 500);
}).then(() => {
return test_utils.waitUntil(() => view.model.get('chat_state') === 'paused', 1000);
}).then(() => test_utils.waitUntil(() => view.model.get('chat_state') === 'inactive', 1000))
.then(() => {
console.log('chat_state set to inactive');
expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
@ -1041,17 +1045,18 @@
_converse.emit('rosterContactsFetched');
test_utils.openControlBox();
const sender_jid = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
let view;
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
spyOn(_converse, 'emit');
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
var view = _converse.chatboxviews.get(sender_jid);
view = _converse.chatboxviews.get(sender_jid);
expect(view.el.querySelectorAll('.chat-event').length).toBe(0);
// Insert <composing> message, to also check that
// text messages are inserted correctly with
// temporary chat events in the chat contents.
var msg = $msg({
const msg = $msg({
'to': _converse.bare_jid,
'xmlns': 'jabber:client',
'from': sender_jid,
@ -1059,14 +1064,18 @@
.c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
.tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(view.el.querySelectorAll('.chat-state-notification').length).toBe(1);
msg = $msg({
const msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
}).c('body').c('inactive', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => (view.model.messages.length > 1));
}).then(() => {
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
expect($(view.el).find('.chat-state-notification').length).toBe(0);
done();
@ -1178,12 +1187,15 @@
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msg);
expect(_converse.incrementMsgCounter).toHaveBeenCalled();
expect(_converse.clearMsgCounter).not.toHaveBeenCalled();
expect(_converse.msg_counter).toBe(1);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
_converse.windowSate = previous_state;
done();
return test_utils.waitUntil(() => _converse.api.chats.get().length)
.then(() => {
expect(_converse.incrementMsgCounter).toHaveBeenCalled();
expect(_converse.clearMsgCounter).not.toHaveBeenCalled();
expect(_converse.msg_counter).toBe(1);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
_converse.windowSate = previous_state;
done();
});
}));
it("is cleared when the window is focused",
@ -1237,7 +1249,8 @@
// initial state
expect(_converse.msg_counter).toBe(0);
var message = 'This message will always increment the message counter from zero',
let view;
const message = 'This message will always increment the message counter from zero',
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
msgFactory = function () {
return $msg({
@ -1254,24 +1267,29 @@
// leave converse-chat page
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory());
expect(_converse.msg_counter).toBe(1);
return test_utils.waitUntil(() => _converse.api.chats.get().length)
.then(() => {
expect(_converse.msg_counter).toBe(1);
// come back to converse-chat page
_converse.saveWindowState(null, 'focus');
var view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(0);
// come back to converse-chat page
_converse.saveWindowState(null, 'focus');
view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(0);
// close chatbox and leave converse-chat page again
view.close();
_converse.windowState = 'hidden';
// close chatbox and leave converse-chat page again
view.close();
_converse.windowState = 'hidden';
// check that msg_counter is incremented from zero again
_converse.chatboxes.onMessage(msgFactory());
view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(1);
done();
// check that msg_counter is incremented from zero again
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => _converse.api.chats.get().length)
}).then(() => {
view = _converse.chatboxviews.get(sender_jid);
expect(u.isVisible(view.el)).toBeTruthy();
expect(_converse.msg_counter).toBe(1);
done();
});
}));
});
@ -1288,13 +1306,17 @@
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost',
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
let view;
test_utils.openChatBoxFor(_converse, sender_jid)
.then((view) => {
.then((v) => {
view = v;
view.model.save('scrolled', true);
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(view.model.get('num_unread')).toBe(1);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is not incremented when the message is received and ChatBoxView is scrolled down",
@ -1323,15 +1345,18 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var msgFactory = function () {
let chatbox;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
};
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(chatbox.get('num_unread')).toBe(1);
done();
});
@ -1344,17 +1369,19 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var msgFactory = function () {
let chatbox;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
};
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(chatbox.get('num_unread')).toBe(1);
done();
});
@ -1368,20 +1395,23 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
let chatbox;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var msgFactory = function () {
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
};
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
const chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(chatbox.get('num_unread')).toBe(1);
_converse.saveWindowState(null, 'focus');
expect(chatbox.get('num_unread')).toBe(0);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is not cleared when ChatBoxView was scrolled up and the windows become focused",
@ -1391,21 +1421,24 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var msgFactory = function () {
let chatbox;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
};
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
_converse.windowState = 'hidden';
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(chatbox.get('num_unread')).toBe(1);
_converse.saveWindowState(null, 'focus');
expect(chatbox.get('num_unread')).toBe(1);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
@ -1419,28 +1452,29 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
let msg, chatbox, indicator_el, selector;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
.then(() => test_utils.openChatBoxFor(_converse, sender_jid))
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
var msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
_converse.chatboxes.onMessage(msg);
var selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator',
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('1');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('2');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is updated when message is received and chatbox is minimized",
@ -1452,28 +1486,30 @@
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let chatbox, indicator_el, msg, selector;
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
.then(() => test_utils.openChatBoxFor(_converse, sender_jid))
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
var chatboxview = _converse.chatboxviews.get(sender_jid);
chatboxview.minimize();
var msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread');
_converse.chatboxes.onMessage(msg);
var selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator',
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
selector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('1');
msg = test_utils.createChatMessage(_converse, sender_jid, 'This message will be unread too');
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
indicator_el = sizzle(selector, _converse.rosterview.el).pop();
expect(indicator_el.textContent).toBe('2');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is cleared when chatbox is maximzied after receiving messages in minimized mode",
@ -1484,27 +1520,28 @@
test_utils.createContacts(_converse, 'current');
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let chatbox, view, select_msgs_indicator;
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
};
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
.then(() => test_utils.openChatBoxFor(_converse, sender_jid))
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
var chatboxview = _converse.chatboxviews.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
view = _converse.chatboxviews.get(sender_jid);
var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
var selectMsgsIndicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
var msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
};
chatboxview.minimize();
select_msgs_indicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
view.minimize();
_converse.chatboxes.onMessage(msgFactory());
expect(selectMsgsIndicator().text()).toBe('1');
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(select_msgs_indicator().text()).toBe('1');
_converse.chatboxes.onMessage(msgFactory());
expect(selectMsgsIndicator().text()).toBe('2');
chatboxview.maximize();
expect(selectMsgsIndicator().length).toBe(0);
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
expect(select_msgs_indicator().text()).toBe('2');
view.maximize();
expect(select_msgs_indicator().length).toBe(0);
done();
});
}));
@ -1518,27 +1555,27 @@
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let view, chatbox, select_msgs_indicator;
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
.then(() => test_utils.openChatBoxFor(_converse, sender_jid))
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
var chatboxview = _converse.chatboxviews.get(sender_jid);
chatbox = _converse.chatboxes.get(sender_jid);
view = _converse.chatboxviews.get(sender_jid);
var msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
};
var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator',
selectMsgsIndicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
select_msgs_indicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
chatbox.save('scrolled', true);
_converse.chatboxes.onMessage(msgFactory());
expect(selectMsgsIndicator().text()).toBe('1');
chatboxview.viewUnreadMessages();
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(select_msgs_indicator().text()).toBe('1');
view.viewUnreadMessages();
_converse.rosterview.render();
expect(selectMsgsIndicator().length).toBe(0);
expect(select_msgs_indicator().length).toBe(0);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is not cleared after user clicks on roster view when chatbox is already opened and scrolled up",
@ -1550,24 +1587,25 @@
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let select_msgs_indicator, view;
test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length, 500)
.then(() => test_utils.openChatBoxFor(_converse, sender_jid))
.then(() => {
var chatbox = _converse.chatboxes.get(sender_jid);
var chatboxview = _converse.chatboxviews.get(sender_jid);
const chatbox = _converse.chatboxes.get(sender_jid);
view = _converse.chatboxviews.get(sender_jid);
var msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
};
var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator',
selectMsgsIndicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
var msgsIndicatorSelector = 'a.open-chat:contains("' + chatbox.get('fullname') + '") .msgs-indicator';
select_msgs_indicator = () => $(_converse.rosterview.el).find(msgsIndicatorSelector);
chatbox.save('scrolled', true);
_converse.chatboxes.onMessage(msgFactory());
expect(selectMsgsIndicator().text()).toBe('1');
test_utils.openChatBoxFor(_converse, sender_jid);
expect(selectMsgsIndicator().text()).toBe('1');
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(select_msgs_indicator().text()).toBe('1');
return test_utils.openChatBoxFor(_converse, sender_jid);
}).then(() => {
expect(select_msgs_indicator().text()).toBe('1');
done();
});
}));
@ -1584,20 +1622,21 @@
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let selectUnreadMsgCount;
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid, 'This message will be received as unread, but eventually will be read');
};
const selectUnreadMsgCount = function () {
selectUnreadMsgCount = function () {
const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid);
return minimizedChatBoxView.el.querySelector('.message-count');
};
const chatbox = _converse.chatboxes.get(sender_jid);
chatbox.save('scrolled', true);
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => chatbox.messages.length);
}).then(() => {
const chatboxview = _converse.chatboxviews.get(sender_jid);
chatboxview.minimize();
@ -1605,7 +1644,7 @@
expect(u.isVisible(unread_count)).toBeTruthy();
expect(unread_count.innerHTML).toBe('1');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("is incremented when message is received and windows is not focused",
@ -1617,27 +1656,27 @@
_converse.emit('rosterContactsFetched');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let selectUnreadMsgCount;
test_utils.openChatBoxFor(_converse, sender_jid)
.then(() => {
const msgFactory = function () {
return test_utils.createChatMessage(_converse, sender_jid,
'This message will be received as unread, but eventually will be read');
};
const selectUnreadMsgCount = function () {
selectUnreadMsgCount = function () {
const minimizedChatBoxView = _converse.minimized_chats.get(sender_jid);
return minimizedChatBoxView.el.querySelector('.message-count');
};
const chatboxview = _converse.chatboxviews.get(sender_jid);
chatboxview.minimize();
const view = _converse.chatboxviews.get(sender_jid);
view.minimize();
_converse.chatboxes.onMessage(msgFactory());
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
const unread_count = selectUnreadMsgCount();
expect(u.isVisible(unread_count)).toBeTruthy();
expect(unread_count.innerHTML).toBe('1');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("will render Openstreetmap-URL from geo-URI",

View File

@ -738,7 +738,6 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza));
jasmine.clock().tick(ONE_DAY_LATER);
// Test a user leaving a groupchat
presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
@ -766,11 +765,9 @@
expect($chat_content.find('div.chat-info:last').html()).toBe(
'newguy has left the groupchat. '+
'"Disconnected: Replaced by new connection"');
jasmine.clock().uninstall();
done();
return;
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows its description in the chat heading",
@ -827,14 +824,14 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
let view;
test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
.then(function () {
return test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname'))
}).then(function () {
.then(() => test_utils.waitUntil(() => _converse.xmppstatus.vcard.get('fullname')))
.then(() => {
test_utils.createContacts(_converse, 'current');
return test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
}).then(function () {
var view = _converse.chatboxviews.get('lounge@localhost');
}).then(() => {
view = _converse.chatboxviews.get('lounge@localhost');
if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
var message = '/me is tired';
var nick = mock.chatroom_names[0],
@ -1495,20 +1492,18 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
let view;
const text = 'This is a received message';
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
spyOn(_converse, 'emit');
var view = _converse.chatboxviews.get('lounge@localhost');
view = _converse.chatboxviews.get('lounge@localhost');
if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
var nick = mock.chatroom_names[0];
view.model.occupants.create({
'nick': nick,
'muc_jid': `${view.model.get('jid')}/${nick}`
});
var text = 'This is a received message';
var message = $msg({
from: 'lounge@localhost/'+nick,
id: '1',
@ -1585,7 +1580,7 @@
}).c('body').t('Message: '+i).tree());
}
// Give enough time for `markScrolled` to have been called
setTimeout(function () {
setTimeout(() => {
view.content.scrollTop = 0;
view.model.onMessage(
$msg({
@ -3435,19 +3430,19 @@
var roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(0);
var room_jid = 'kitchen@conference.shakespeare.lit';
test_utils.openAndEnterChatRoom(
_converse, 'kitchen', 'conference.shakespeare.lit', 'fires').then(function () {
let view, nick;
const room_jid = 'kitchen@conference.shakespeare.lit';
const message = 'fires: Your attention is required';
test_utils.openAndEnterChatRoom(_converse, 'kitchen', 'conference.shakespeare.lit', 'fires')
.then(() => {
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(0);
var view = _converse.chatboxviews.get(room_jid);
view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
var message = 'fires: Your attention is required';
var nick = mock.chatroom_names[0];
nick = mock.chatroom_names[0];
view.model.onMessage($msg({
from: room_jid+'/'+nick,
@ -3455,7 +3450,8 @@
to: 'dummy@localhost',
type: 'groupchat'
}).c('body').t(message).tree());
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('1');
@ -3466,17 +3462,16 @@
'to': 'dummy@localhost',
'type': 'groupchat'
}).c('body').t(message).tree());
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
expect(roomspanel.el.querySelector('.msgs-indicator').textContent).toBe('2');
view.model.set({'minimized': false});
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(0);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
describe("A Chat Status Notification", function () {
@ -3488,14 +3483,15 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
let view;
const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openAndEnterChatRoom(
_converse, 'coven', 'chat.shakespeare.lit', 'some1').then(function () {
var room_jid = 'coven@chat.shakespeare.lit';
var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
var $chat_content = $(view.el).find('.chat-content');
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content');
expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat");
expect($(chat_content).find('div.chat-info:first').html()).toBe("some1 has entered the groupchat");
let presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
@ -3508,8 +3504,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(2);
expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the groupchat");
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
expect($(chat_content).find('div.chat-info:last').html()).toBe("newguy has entered the groupchat");
presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
@ -3522,8 +3518,8 @@
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect($chat_content[0].querySelectorAll('div.chat-info').length).toBe(3);
expect($chat_content.find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat");
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3);
expect($(chat_content).find('div.chat-info:last').html()).toBe("nomorenicks has entered the groupchat");
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
@ -3536,7 +3532,8 @@
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg);
return test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length);
}).then(() => {
// Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
@ -3554,7 +3551,7 @@
});
// Check that it doesn't appear twice
msg = $msg({
let msg = $msg({
from: room_jid+'/newguy',
id: (new Date()).getTime(),
to: 'dummy@localhost',
@ -3632,7 +3629,7 @@
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
@ -3776,7 +3773,7 @@
expect(notifications[0].textContent).toEqual('nomorenicks is typing');
expect(notifications[1].textContent).toEqual('newguy has stopped typing');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
});

View File

@ -1,11 +1,12 @@
(function (root, factory) {
define(["jquery", "jasmine", "mock", "test-utils"], factory);
} (this, function ($, jasmine, mock, test_utils) {
var _ = converse.env._;
var $pres = converse.env.$pres;
var $msg = converse.env.$msg;
var $iq = converse.env.$iq;
var u = converse.env.utils;
const _ = converse.env._,
$pres = converse.env.$pres,
$msg = converse.env.$msg,
$iq = converse.env.$iq,
u = converse.env.utils,
Strophe = converse.env.Strophe;
describe("The Controlbox", function () {
@ -72,18 +73,18 @@
test_utils.createContacts(_converse, 'all').openControlBox();
_converse.emit('rosterContactsFetched');
let chatview;
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, sender_jid);
return test_utils.waitUntil(() => _converse.chatboxes.length).then(() => {
const chatview = _converse.chatboxviews.get(sender_jid);
chatview = _converse.chatboxviews.get(sender_jid);
chatview.model.set({'minimized': true});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
var msg = $msg({
const msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
@ -91,10 +92,13 @@
}).c('body').t('hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => _converse.rosterview.el.querySelectorAll(".msgs-indicator"));
}).then(() => {
spyOn(chatview.model, 'incrementUnreadMsgCounter').and.callThrough();
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
msg = $msg({
const msg = $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
@ -102,14 +106,15 @@
}).c('body').t('hello again').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
return test_utils.waitUntil(() => chatview.model.incrementUnreadMsgCounter.calls.count());
}).then(() => {
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2');
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
chatview.model.set({'minimized': false});
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}));
});

View File

@ -311,7 +311,7 @@
}).catch(_.partial(console.error, _));
}));
it("has a method 'open' which opens and returns promise that resolves to a chat model", mock.initConverseWithPromises(
it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesInitialized'], {}, function (done, _converse) {
test_utils.openControlBox();

View File

@ -20,15 +20,27 @@
null, ['discoInitialized'], {},
function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand').then(function () {
var chatroomview = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
var stanza = Strophe.xmlHtmlNode(
let view, stanza;
test_utils.openAndEnterChatRoom(_converse, 'trek-radio', 'conference.lightwitch.org', 'jcbrand')
.then(() => {
view = _converse.chatboxviews.get('trek-radio@conference.lightwitch.org');
stanza = Strophe.xmlHtmlNode(
`<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452" type="groupchat" from="trek-radio@conference.lightwitch.org/comndrdukath#0805 (STO)">
<body>negan</body>
<stanza-id xmlns="urn:xmpp:sid:0" id="45fbbf2a-1059-479d-9283-c8effaf05621" by="trek-radio@conference.lightwitch.org"/>
</message>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length)
}).then(() => {
// XXX: we wait here until the first message appears before
// sending the duplicate. If we don't do that, then the
// duplicate appears before the promise for `createMessage`
// has been resolved, which means that the `isDuplicate`
// check fails because the first message doesn't exist yet.
//
// Not sure whether such a race-condition might pose a problem
// in "real-world" situations.
stanza = Strophe.xmlHtmlNode(
`<message xmlns="jabber:client" to="jcbrand@lightwitch.org/converse.js-73057452">
<result xmlns="urn:xmpp:mam:2" queryid="82d9db27-6cf8-4787-8c2c-5a560263d823" id="45fbbf2a-1059-479d-9283-c8effaf05621">
@ -39,10 +51,14 @@
</forwarded>
</result>
</message>`).firstElementChild;
chatroomview.model.onMessage(stanza);
expect(chatroomview.content.querySelectorAll('.chat-msg').length).toBe(1);
spyOn(view.model, 'isDuplicate').and.callThrough();
view.model.onMessage(stanza);
return test_utils.waitUntil(() => view.model.isDuplicate.calls.count());
}).then(() => {
expect(view.content.querySelectorAll('.chat-msg').length).toBe(1);
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}))
});

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
const _ = converse.env._;
const $msg = converse.env.$msg;
const u = converse.env.utils;
const Strophe = converse.env.Strophe;
describe("The Minimized Chats Widget", function () {
@ -43,7 +44,7 @@
expect(_converse.minimized_chats.keys().length).toBe(2);
expect(_.includes(_converse.minimized_chats.keys(), contact_jid)).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("can be toggled to hide or show minimized chats",
@ -74,7 +75,7 @@
}).then(() => {
expect(_converse.minimized_chats.toggleview.model.get('collapsed')).toBeTruthy();
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows the number messages received to minimized chats",
@ -99,7 +100,7 @@
contact_jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
}
return test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
test_utils.waitUntil(() => _converse.chatboxes.length == 4).then(() => {
for (i=0; i<3; i++) {
chatview = _converse.chatboxviews.get(contact_jid);
chatview.model.set({'minimized': true});
@ -111,9 +112,11 @@
}).c('body').t('This message is sent to a minimized chatbox').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i+1).toString());
}
return test_utils.waitUntil(() => chatview.model.messages.length);
}).then(() => {
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((3).toString());
// Chat state notifications don't increment the unread messages counter
// <composing> state
_converse.chatboxes.onMessage($msg({
@ -122,7 +125,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('composing', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <paused> state
_converse.chatboxes.onMessage($msg({
@ -131,7 +134,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('paused', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <gone> state
_converse.chatboxes.onMessage($msg({
@ -140,7 +143,7 @@
type: 'chat',
id: (new Date()).getTime()
}).c('gone', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
// <inactive> state
_converse.chatboxes.onMessage($msg({
@ -149,9 +152,9 @@
type: 'chat',
id: (new Date()).getTime()
}).c('inactive', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe((i).toString());
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe((i).toString());
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows the number messages received to minimized groupchats",
@ -159,7 +162,7 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
var room_jid = 'kitchen@conference.shakespeare.lit';
const room_jid = 'kitchen@conference.shakespeare.lit';
test_utils.openAndEnterChatRoom(
_converse, 'kitchen', 'conference.shakespeare.lit', 'fires').then(function () {
var view = _converse.chatboxviews.get(room_jid);
@ -175,11 +178,12 @@
type: 'groupchat'
}).c('body').t(message).tree();
view.model.onMessage(msg);
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe('1');
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(u.isVisible(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count'))).toBeTruthy();
expect(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count').textContent).toBe('1');
done();
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
}));

971
spec/omemo.js Normal file
View File

@ -0,0 +1,971 @@
(function (root, factory) {
define(["jasmine", "mock", "test-utils"], factory);
} (this, function (jasmine, mock, test_utils) {
var Strophe = converse.env.Strophe;
var b64_sha1 = converse.env.b64_sha1;
var $iq = converse.env.$iq;
var $msg = converse.env.$msg;
var _ = converse.env._;
var u = converse.env.utils;
function deviceListFetched (_converse, jid) {
return _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
).pop(), 'nodeTree');
}
function ownDeviceHasBeenPublished (_converse) {
return _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
).pop(), 'nodeTree');
}
function bundleHasBeenPublished (_converse) {
return _.get(_.filter(
_converse.connection.IQ_stanzas,
iq => iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]')
).pop(), 'nodeTree');
}
function bundleFetched (_converse, jid, device_id) {
return _.get(_.filter(
_converse.connection.IQ_stanzas,
(iq) => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
).pop(), 'nodeTree');
}
function initializedOMEMO (_converse) {
return test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
.then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized');
});
}
describe("The OMEMO module", function() {
it("adds methods for encrypting and decrypting messages via AES GCM",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
const message = 'This message will be encrypted'
let view;
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid)
.then((v) => {
view = v;
return view.model.encryptMessage(message);
}).then((payload) => {
return view.model.decryptMessage(payload);
}).then((result) => {
expect(result).toBe(message);
done();
});
}));
it("enables encrypted messages to be sent and received",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
let view, sent_stanza;
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.waitUntil(() => initializedOMEMO(_converse))
.then(() => test_utils.openChatBoxFor(_converse, contact_jid))
.then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)))
.then(iq_stanza => {
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.connection.jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => {
const devicelist = _converse.devicelists.get({'jid': contact_jid});
expect(devicelist.devices.length).toBe(1);
view = _converse.chatboxviews.get(contact_jid);
view.model.set('omemo_active', true);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This message will be encrypted';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13 // Enter
});
return test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
}).then((iq_stanza) => {
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t(btoa('3333')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
.c('signedPreKeySignature').t(btoa('200000')).up()
.c('identityKey').t(btoa('300000')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => sent_stanza);
}).then(() => {
expect(sent_stanza.toLocaleString()).toBe(
`<message from='dummy@localhost/resource' to='max.frankfurter@localhost' `+
`type='chat' id='${sent_stanza.nodeTree.getAttribute('id')}' xmlns='jabber:client'>`+
`<body>This is an OMEMO encrypted message which your client doesnt seem to support. Find more information on https://conversations.im/omemo</body>`+
`<encrypted xmlns='eu.siacs.conversations.axolotl'>`+
`<header sid='123456789'>`+
`<key rid='482886413b977930064a5888b92134fe'>eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9</key>`+
`<key rid='555'>eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9</key>`+
`<iv>${sent_stanza.nodeTree.querySelector('iv').textContent}</iv>`+
`</header>`+
`<payload>${sent_stanza.nodeTree.querySelector('payload').textContent}</payload>`+
`</encrypted>`+
`<store xmlns='urn:xmpp:hints'/>`+
`</message>`);
// Test reception of an encrypted message
return view.model.encryptMessage('This is an encrypted message from the contact')
}).then((obj) => {
// XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message.
const key = btoa(JSON.stringify({
'type': 1,
'body': obj.key_and_tag,
'registrationId': '1337'
}));
const stanza = $msg({
'from': contact_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': 'qwerty'
}).c('body').t('This is a fallback message').up()
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('header', {'sid': '555'})
.c('key', {'rid': _converse.omemo_store.get('device_id')}).t(key).up()
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(obj.payload);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
expect(view.model.messages.length).toBe(2);
const last_msg = view.model.messages.at(1);
expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
.toBe('This is an encrypted message from the contact');
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("can receive a PreKeySignalMessage",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
_converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
let view, sent_stanza;
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.waitUntil(() => initializedOMEMO(_converse))
.then(() => _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact'))
.then(obj => {
// XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message.
const key = btoa(JSON.stringify({
'type': 1,
'body': obj.key_and_tag,
'registrationId': '1337'
}));
const stanza = $msg({
'from': contact_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': 'qwerty'
}).c('body').t('This is a fallback message').up()
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('header', {'sid': '555'})
.c('key', {'prekey': 'true', 'rid': _converse.omemo_store.get('device_id')}).t(key).up()
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(obj.payload);
const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
// Since it's difficult to override
// decryptPreKeyWhisperMessage, where a prekey will be
// removed from the store, we do it here, before the
// missing prekeys are generated.
_converse.omemo_store.removePreKey(1);
return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid))
}).then(iq_stanza => deviceListFetched(_converse, contact_jid))
.then(iq_stanza => {
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.connection.jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
// XXX: the bundle gets published twice, we want to make sure
// that we wait for the 2nd, so we clear all the already sent
// stanzas.
_converse.connection.IQ_stanzas = [];
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => test_utils.waitUntil(() => bundleHasBeenPublished(_converse)))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
`<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
`<item>`+
`<bundle xmlns="eu.siacs.conversations.axolotl">`+
`<signedPreKeyPublic signedPreKeyId="0">${btoa('1234')}</signedPreKeyPublic>`+
`<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
`<identityKey>${btoa('1234')}</identityKey>`+
`<prekeys>`+
`<preKeyPublic preKeyId="0">${btoa('1234')}</preKeyPublic>`+
`<preKeyPublic preKeyId="1">${btoa('1234')}</preKeyPublic>`+
`<preKeyPublic preKeyId="2">${btoa('1234')}</preKeyPublic>`+
`<preKeyPublic preKeyId="3">${btoa('1234')}</preKeyPublic>`+
`<preKeyPublic preKeyId="4">${btoa('1234')}</preKeyPublic>`+
`</prekeys>`+
`</bundle>`+
`</item>`+
`</publish>`+
`</pubsub>`+
`</iq>`)
const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id'));
expect(own_device.get('bundle').prekeys.length).toBe(5);
expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled();
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("updates device lists based on PEP messages",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// Wait until own devices are fetched
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
'<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
'</pubsub>'+
'</iq>');
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => {
expect(_converse.devicelists.length).toBe(1);
const devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized');
}).then(() => {
let stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_01',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
.c('item')
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('device', {'id': '1234'})
.c('device', {'id': '4223'})
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
let devices = _converse.devicelists.get(contact_jid).devices;
expect(devices.length).toBe(2);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_02',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
.c('item')
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('device', {'id': '4223'})
.c('device', {'id': '4224'})
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
expect(devices.length).toBe(2);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224');
// Check that own devicelist gets updated
stanza = $msg({
'from': _converse.bare_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_03',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
.c('item')
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('device', {'id': '123456789'})
.c('device', {'id': '555'})
.c('device', {'id': '777'})
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
devices = _converse.devicelists.get(_converse.bare_jid).devices;
expect(devices.length).toBe(3);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
_converse.connection.IQ_stanzas = [];
// Check that own device gets re-added
stanza = $msg({
'from': _converse.bare_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_04',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
.c('item')
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('device', {'id': '444'})
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
}).then(iq_stanza => {
// Check that our own device is added again, but that removed
// devices are not added.
expect(iq_stanza.outerHTML).toBe(
'<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<publish node="eu.siacs.conversations.axolotl.devicelist">'+
'<item>'+
'<list xmlns="eu.siacs.conversations.axolotl">'+
'<device id="123456789"/>'+
'<device id="444"/>'+
'</list>'+
'</item>'+
'</publish>'+
'</pubsub>'+
'</iq>');
expect(_converse.devicelists.length).toBe(2);
const devices = _converse.devicelists.get(_converse.bare_jid).devices;
// The device id for this device (123456789) was also generated and added to the list,
// which is why we have 2 devices now.
expect(devices.length).toBe(2);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("updates device bundles based on PEP messages",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
'<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
'</pubsub>'+
'</iq>');
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => {
expect(_converse.devicelists.length).toBe(1);
const devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized');
}).then(() => {
let stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_01',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
.c('signedPreKeySignature').t('2222').up()
.c('identityKey').t('3333').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1001'}).up()
.c('preKeyPublic', {'preKeyId': '1002'}).up()
.c('preKeyPublic', {'preKeyId': '1003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
let devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(1);
let device = devicelist.devices.at(0);
expect(device.get('bundle').identity_key).toBe('3333');
expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
expect(device.get('bundle').signed_prekey.id).toBe(4223);
expect(device.get('bundle').signed_prekey.signature).toBe('2222');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(1001);
expect(device.get('bundle').prekeys[1].id).toBe(1002);
expect(device.get('bundle').prekeys[2].id).toBe(1003);
stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_02',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
.c('signedPreKeySignature').t('6666').up()
.c('identityKey').t('7777').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '2001'}).up()
.c('preKeyPublic', {'preKeyId': '2002'}).up()
.c('preKeyPublic', {'preKeyId': '2003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(1);
device = devicelist.devices.at(0);
expect(device.get('bundle').identity_key).toBe('7777');
expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
expect(device.get('bundle').signed_prekey.id).toBe(4223);
expect(device.get('bundle').signed_prekey.signature).toBe('6666');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(2001);
expect(device.get('bundle').prekeys[1].id).toBe(2002);
expect(device.get('bundle').prekeys[2].id).toBe(2003);
stanza = $msg({
'from': _converse.bare_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_03',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
.c('signedPreKeySignature').t('3333').up()
.c('identityKey').t('1111').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '3001'}).up()
.c('preKeyPublic', {'preKeyId': '3002'}).up()
.c('preKeyPublic', {'preKeyId': '3003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
device = devicelist.devices.at(1);
expect(device.get('bundle').identity_key).toBe('1111');
expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
expect(device.get('bundle').signed_prekey.id).toBe(9999);
expect(device.get('bundle').signed_prekey.signature).toBe('3333');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(3001);
expect(device.get('bundle').prekeys[1].id).toBe(3002);
expect(device.get('bundle').prekeys[2].id).toBe(3003);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("publishes a bundle with which an encrypted session can be created",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
_converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
.then(iq_stanza => {
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(1);
return test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => ownDeviceHasBeenPublished(_converse))
.then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
}).then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
`<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
`<item>`+
`<bundle xmlns="eu.siacs.conversations.axolotl">`+
`<signedPreKeyPublic signedPreKeyId="0">${btoa('1234')}</signedPreKeyPublic>`+
`<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
`<identityKey>${btoa('1234')}</identityKey>`+
`<prekeys>`+
`<preKeyPublic preKeyId="0">${btoa('1234')}</preKeyPublic>`+
`<preKeyPublic preKeyId="1">${btoa('1234')}</preKeyPublic>`+
`</prekeys>`+
`</bundle>`+
`</item>`+
`</publish>`+
`</pubsub>`+
`</iq>`)
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized');
}).then(done).catch(_.partial(console.error, _));
}));
it("adds a toolbar button for starting an encrypted chat session",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
let modal;
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
'<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
'</pubsub>'+
'</iq>');
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => {
expect(_converse.devicelists.length).toBe(1);
const devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
// Check that own device was published
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
}).then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
'<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<publish node="eu.siacs.conversations.axolotl.devicelist">'+
'<item>'+
'<list xmlns="eu.siacs.conversations.axolotl">'+
'<device id="482886413b977930064a5888b92134fe"/>'+
'<device id="123456789"/>'+
'</list>'+
'</item>'+
'</publish>'+
'</pubsub>'+
'</iq>');
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
}).then(iq_stanza => {
expect(iq_stanza.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
expect(iq_stanza.querySelector('prekeys').childNodes.length).toBe(100);
const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
expect(signed_prekeys.length).toBe(1);
const signed_prekey = signed_prekeys[0];
expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized', 1000);
}).then(() => {
return test_utils.openChatBoxFor(_converse, contact_jid);
}).then(() => {
return test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
}).then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
'<iq type="get" from="dummy@localhost" to="'+contact_jid+'" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
'</pubsub>'+
'</iq>');
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
.c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
.c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
.c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const devicelist = _converse.devicelists.get(contact_jid);
return test_utils.waitUntil(() => devicelist.devices.length);
}).then(() => {
expect(_converse.devicelists.length).toBe(2);
const devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(4);
expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
}).then(() => {
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = view.el.querySelector('.chat-toolbar');
expect(view.model.get('omemo_active')).toBe(undefined);
const toggle = toolbar.querySelector('.toggle-omemo');
expect(_.isNull(toggle)).toBe(false);
expect(u.hasClass('fa-unlock', toggle)).toBe(true);
expect(u.hasClass('fa-lock', toggle)).toBe(false);
spyOn(view, 'toggleOMEMO').and.callThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
toolbar.querySelector('.toggle-omemo').click();
expect(view.toggleOMEMO).toHaveBeenCalled();
expect(view.model.get('omemo_active')).toBe(true);
return test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
}).then(() => {
const view = _converse.chatboxviews.get(contact_jid);
const toolbar = view.el.querySelector('.chat-toolbar');
const toggle = toolbar.querySelector('.toggle-omemo');
expect(u.hasClass('fa-unlock', toggle)).toBe(false);
expect(u.hasClass('fa-lock', toggle)).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This message will be sent encrypted';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13
});
done();
}).catch(_.partial(console.error, _));
}));
it("shows OMEMO device fingerprints in the user details modal",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
let modal;
test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => {
// We simply emit, to avoid doing all the setup work
_converse.emit('OMEMOInitialized');
const view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
modal = view.user_details_modal;
return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
}).then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
`<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
`</iq>`);
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
}).then(() => test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555')))
.then(iq_stanza => {
expect(iq_stanza.outerHTML).toBe(
`<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
`</pubsub>`+
`</iq>`);
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const view = _converse.chatboxviews.get(contact_jid);
const modal = view.user_details_modal;
return test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
}).then(() => {
const view = _converse.chatboxviews.get(contact_jid);
const modal = view.user_details_modal;
expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
const el = modal.el.querySelector('.fingerprints .fingerprint');
expect(el.textContent.trim()).toBe(
u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
);
expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
const devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.get('555').get('trusted')).toBe(0);
let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
expect(trusted_radio.checked).toBe(true);
let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
expect(untrusted_radio.checked).toBe(false);
// Test that the device can be set to untrusted
untrusted_radio.click();
trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
expect(trusted_radio.hasAttribute('checked')).toBe(false);
expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
expect(untrusted_radio.hasAttribute('checked')).toBe(true);
trusted_radio.click();
expect(devicelist.devices.get('555').get('trusted')).toBe(1);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
});
describe("A chatbox with an active OMEMO session", function() {
it("will not show the spoiler toolbar button",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
// TODO
done()
}));
});
}));

View File

@ -47,7 +47,7 @@
"<presence xmlns='jabber:client'>"+
"<status>Hello world</status>"+
"<priority>0</priority>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>"
);
_converse.priority = 2;
@ -57,7 +57,7 @@
"<show>away</show>"+
"<status>Going jogging</status>"+
"<priority>2</priority>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>"
);
@ -68,7 +68,7 @@
"<show>dnd</show>"+
"<status>Doing taxes</status>"+
"<priority>0</priority>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>"
);
}));
@ -97,7 +97,7 @@
.toBe("<presence xmlns='jabber:client'>"+
"<status>My custom status</status>"+
"<priority>0</priority>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>")
return test_utils.waitUntil(function () {
@ -113,7 +113,7 @@
modal.el.querySelector('[type="submit"]').click();
expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString())
.toBe("<presence xmlns='jabber:client'><show>dnd</show><status>My custom status</status><priority>0</priority>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='wmJWAEmiBuDhg0VUoDmqHp3qXJ0='/>"+
"<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='ggltNSI5YG/7dFKB57Bk2dRYRU0='/>"+
"</presence>")
done();
});

View File

@ -229,14 +229,16 @@
// have to mock stanza traffic.
}, function (done, _converse) {
let view, nick;
const room_jid = 'kitchen@conference.shakespeare.lit';
test_utils.waitUntil(() => !_.isUndefined(_converse.rooms_list_view), 500)
.then(() => test_utils.openAndEnterChatRoom(_converse, 'kitchen', 'conference.shakespeare.lit', 'romeo'))
.then(() => {
const room_jid = 'kitchen@conference.shakespeare.lit';
const view = _converse.chatboxviews.get(room_jid);
view = _converse.chatboxviews.get(room_jid);
view.model.set({'minimized': true});
const contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
const nick = mock.chatroom_names[0];
nick = mock.chatroom_names[0];
view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
@ -260,9 +262,11 @@
type: 'groupchat'
}).c('body').t('romeo: Your attention is required').tree()
);
var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator"));
}).then(() => {
spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
const indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('1');
view.model.onMessage(
$msg({
from: room_jid+'/'+nick,
@ -271,14 +275,16 @@
type: 'groupchat'
}).c('body').t('romeo: and another thing...').tree()
);
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
return test_utils.waitUntil(() => view.model.incrementUnreadMsgCounter.calls.count());
}).then(() => {
let indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(indicator_el.textContent).toBe('2');
// When the chat gets maximized again, the unread indicators are removed
view.model.set({'minimized': false});
indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
expect(_.isNull(indicator_el));
room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
const room_el = _converse.rooms_list_view.el.querySelector(".available-chatroom");
expect(_.includes(room_el.classList, 'unread-msgs')).toBeFalsy();
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));

View File

@ -389,7 +389,6 @@
function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render();
test_utils.openControlBox();
@ -430,7 +429,6 @@
function (done, _converse) {
_converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render();
@ -477,7 +475,6 @@
_converse.roster_groups = true;
var groups = ['colleagues', 'friends'];
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox();
_converse.rosterview.render();
@ -576,7 +573,6 @@
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox();
_converse.roster.create({
@ -726,7 +722,6 @@
var i, t;
test_utils.openControlBox();
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
for (i=0; i<mock.pend_names.length; i++) {
_converse.roster.create({
@ -908,7 +903,6 @@
test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group li').length, 700)
.then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
@ -935,7 +929,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
@ -963,7 +956,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
@ -991,7 +983,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
@ -1020,7 +1011,6 @@
}, 500)
.then(function () {
var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) {
@ -1151,7 +1141,6 @@
names.push($(item).text().replace(/^\s+|\s+$/g, ''));
}
};
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough();
spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
for (i=0; i<mock.req_names.length; i++) {

View File

@ -78,8 +78,9 @@
remove_contact_button.click();
return test_utils.waitUntil(() => u.isVisible(document.querySelector('.alert-danger')), 2000);
}).then(function () {
expect(document.querySelector('.alert-danger .modal-title').textContent).toBe("Error");
expect(document.querySelector('.modal:not(#user-profile-modal) .modal-body p').textContent.trim())
const header = document.querySelector('.alert-danger .modal-title');
expect(header.textContent).toBe("Error");
expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
.toBe("Sorry, there was an error while trying to remove Max Frankfurter as a contact.");
document.querySelector('.alert-danger button.close').click();
const show_modal_button = view.el.querySelector('.show-user-details-modal');

View File

@ -577,8 +577,10 @@
// Add a handler for bookmarks pushed from other connected clients
// (from the same user obviously)
_converse.connection.addHandler((message) => {
if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
_converse.bookmarks.createBookmarksFromStanza(message);
if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) {
_converse.api.waitUntil('bookmarksInitialized')
.then(() => _converse.bookmarks.createBookmarksFromStanza(message))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
}, null, 'message', 'headline', null, _converse.bare_jid);
});

View File

@ -265,7 +265,7 @@
this.messages.on('change:upload', (message) => {
if (message.get('upload') === _converse.SUCCESS) {
this.sendMessageStanza(message);
this.sendMessageStanza(this.createMessageStanza(message));
}
});
@ -295,8 +295,16 @@
const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop();
if (replace) {
const msgid = replace && replace.getAttribute('id') || stanza.getAttribute('id'),
message = msgid && this.messages.findWhere({msgid}),
older_versions = message.get('older_versions') || [];
message = msgid && this.messages.findWhere({msgid});
if (!message) {
// XXX: Looks like we received a correction for a
// non-existing message, probably due to MAM.
// Not clear what can be done about this... we'll
// just create it as a separate message for now.
return false;
}
const older_versions = message.get('older_versions') || [];
older_versions.push(message.get('message'));
message.save({
'message': _converse.chatboxes.getMessageBody(stanza),
@ -355,39 +363,37 @@
return stanza;
},
sendMessageStanza (message) {
const messageStanza = this.createMessageStanza(message);
_converse.connection.send(messageStanza);
sendMessageStanza (stanza) {
_converse.connection.send(stanza);
if (_converse.forward_messages) {
// Forward the message, so that other connected resources are also aware of it.
_converse.connection.send(
$msg({
'to': _converse.bare_jid,
'type': this.get('message_type'),
'id': message.get('msgid')
}).c('forwarded', {'xmlns': Strophe.NS.FORWARD})
.c('delay', {
'xmns': Strophe.NS.DELAY,
'stamp': moment().format()
}).up()
.cnode(messageStanza.tree())
.cnode(stanza.tree())
);
}
},
getOutgoingMessageAttributes (text, spoiler_hint) {
const fullname = _converse.xmppstatus.get('fullname'),
is_spoiler = this.get('composing_spoiler');
return {
'fullname': fullname,
const is_spoiler = this.get('composing_spoiler');
return _.extend(this.toJSON(), {
'id': _converse.connection.getUniqueId(),
'fullname': _converse.xmppstatus.get('fullname'),
'from': _converse.bare_jid,
'sender': 'me',
'time': moment().format(),
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
'is_spoiler': is_spoiler,
'spoiler_hint': is_spoiler ? spoiler_hint : undefined
};
'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
'type': this.get('message_type')
});
},
sendMessage (attrs) {
@ -396,7 +402,7 @@
* Parameters:
* (Message) message - The chat message
*/
const message = this.messages.findWhere('correcting')
let message = this.messages.findWhere('correcting')
if (message) {
const older_versions = message.get('older_versions') || [];
older_versions.push(message.get('message'));
@ -407,9 +413,10 @@
'older_versions': older_versions,
'references': attrs.references
});
return this.sendMessageStanza(message);
} else {
message = this.messages.create(attrs);
}
return this.sendMessageStanza(this.messages.create(attrs));
return this.sendMessageStanza(this.createMessageStanza(message));
},
sendChatState () {
@ -453,8 +460,7 @@
this.getOutgoingMessageAttributes(), {
'file': file,
'progress': 0,
'slot_request_url': slot_request_url,
'type': this.get('message_type'),
'slot_request_url': slot_request_url
})
);
}
@ -512,11 +518,7 @@
if (attrs.type === 'groupchat') {
attrs.from = stanza.getAttribute('from');
attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from));
if (Strophe.getResourceFromJid(attrs.from) === this.get('nick')) {
attrs.sender = 'me';
} else {
attrs.sender = 'them';
}
attrs.sender = attrs.nick === this.get('nick') ? 'me': 'them';
} else {
attrs.from = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
if (attrs.from === _converse.bare_jid) {
@ -541,17 +543,27 @@
/* Create a Backbone.Message object inside this chat box
* based on the identified message stanza.
*/
const attrs = this.getMessageAttributesFromStanza(message, original_stanza);
const is_csn = u.isOnlyChatStateNotification(attrs);
if (is_csn && (attrs.is_delayed || (attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick')))) {
// XXX: MUC leakage
// No need showing delayed or our own CSN messages
return;
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom)
return;
const that = this;
function _create (attrs) {
const is_csn = u.isOnlyChatStateNotification(attrs);
if (is_csn && (attrs.is_delayed ||
(attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == that.get('nick')))) {
// XXX: MUC leakage
// No need showing delayed or our own CSN messages
return;
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom)
return;
} else {
return that.messages.create(attrs);
}
}
const result = this.getMessageAttributesFromStanza(message, original_stanza)
if (typeof result.then === "function") {
return new Promise((resolve, reject) => result.then(attrs => resolve(_create(attrs))));
} else {
return this.messages.create(attrs);
const message = _create(result)
return Promise.resolve(message);
}
},
@ -621,7 +633,7 @@
onConnected () {
this.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1(`converse.chatboxes-${_converse.bare_jid}`));
`converse.chatboxes-${_converse.bare_jid}`);
this.registerMessageHandler();
this.fetch({
'add': true,
@ -728,8 +740,11 @@
if (chatbox && !chatbox.handleMessageCorrection(stanza)) {
const msgid = stanza.getAttribute('id'),
message = msgid && chatbox.messages.findWhere({msgid});
if (!message) { // Only create the message when we're sure it's not a duplicate
chatbox.incrementUnreadMsgCounter(chatbox.createMessage(stanza, original_stanza));
if (!message) {
// Only create the message when we're sure it's not a duplicate
chatbox.createMessage(stanza, original_stanza)
.then(msg => chatbox.incrementUnreadMsgCounter(msg))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
}
_converse.emit('message', {'stanza': original_stanza, 'chatbox': chatbox});

View File

@ -110,9 +110,11 @@
emojione.ascii = true;
function onWindowStateChanged (data) {
_converse.chatboxviews.each(function (chatboxview) {
chatboxview.onWindowStateChanged(data.state);
});
if (_converse.chatboxviews) {
_converse.chatboxviews.each(chatboxview => {
chatboxview.onWindowStateChanged(data.state);
});
}
}
_converse.api.listen.on('windowStateChanged', onWindowStateChanged);
@ -228,32 +230,30 @@
events: {
'click button.remove-contact': 'removeContact',
'click button.refresh-contact': 'refreshContact'
'click button.refresh-contact': 'refreshContact',
'click .fingerprint-trust .btn input': 'toggleDeviceTrust'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('contactAdded', this.registerContactEventHandlers, this);
this.model.on('change', this.render, this);
this.registerContactEventHandlers();
_converse.emit('userDetailsModalInitialized', this.model);
},
toHTML () {
return tpl_user_details_modal(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'_': _,
'__': __,
'view': this,
'_converse': _converse,
'allow_contact_removal': _converse.allow_contact_removal,
'alt_profile_image': __("The User's Profile Image"),
'display_name': this.model.getDisplayName(),
'is_roster_contact': !_.isUndefined(this.model.contact),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_jid': __('Jabber ID'),
'label_nickname': __('Nickname'),
'label_remove': __('Remove as contact'),
'label_refresh': __('Refresh'),
'label_role': __('Role'),
'label_url': __('URL')
'utils': u
}));
},
@ -379,6 +379,7 @@
this.addSpoilerButton(options);
this.addFileUploadButton();
this.insertEmojiPicker();
_converse.emit('renderToolbar', this);
return this;
},
@ -737,14 +738,16 @@
if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) &&
previous_el.getAttribute('data-from') === from &&
date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes'))) {
date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', el);
}
if (!next_el) { return; }
if (!u.hasClass('chat-msg--action', 'el') &&
next_el.getAttribute('data-from') === from &&
moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) &&
el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) {
u.addClass('chat-msg--followup', next_el);
} else {
u.removeClass('chat-msg--followup', next_el);

View File

@ -86,6 +86,7 @@
'converse-muc',
'converse-muc-views',
'converse-notification',
'converse-omemo',
'converse-oauth',
'converse-ping',
'converse-profile',

View File

@ -6,7 +6,6 @@
/* This is a Converse.js plugin which add support for XEP-0030: Service Discovery */
/*global Backbone, define, window */
(function (root, factory) {
define(["converse-core", "sizzle"], factory);
}(this, function (converse, sizzle) {

View File

@ -128,13 +128,21 @@
//
// New functions which don't exist yet can also be added.
ChatBox: {
getMessageAttributesFromStanza (message, original_stanza) {
const attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
const archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
attrs.archive_id = archive_id;
function _process (attrs) {
const archive_id = getMessageArchiveID(original_stanza);
if (archive_id) {
attrs.archive_id = archive_id;
}
return attrs;
}
const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments)
if (result instanceof Promise) {
return new Promise((resolve, reject) => result.then((attrs) => resolve(_process(attrs))).catch(reject));
} else {
return _process(result);
}
return attrs;
}
},

View File

@ -158,7 +158,8 @@
_.partial(u.renderImageURL, _converse))(url);
}
let text = this.model.get('message');
const encrypted = this.model.get('encrypted');
let text = encrypted ? this.model.get('plaintext') : this.model.get('message');
if (is_me_message) {
text = text.replace(/^\/me/, '');
}

View File

@ -428,11 +428,7 @@
this.toggleview = new _converse.MinimizedChatsToggleView({
'model': new _converse.MinimizedChatsToggle({'id': id})
});
try {
this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id);
} catch (e) {
debugger;
}
this.toggleview.model.browserStorage = new Backbone.BrowserStorage[storage](id);
this.toggleview.model.fetch();
},

View File

@ -65,6 +65,16 @@
}
});
_converse.api.listen.on('afterTearDown', () => {
if (!_converse.chatboxviews) {
return;
}
const container = _converse.chatboxviews.el.querySelector("#converse-modals");
if (container) {
container.innerHTML = '';
}
});
/************************ BEGIN API ************************/
// We extend the default converse.js API to add methods specific to MUC chat rooms.

View File

@ -926,7 +926,9 @@
if (sender === '') {
return;
}
this.incrementUnreadMsgCounter(this.createMessage(stanza, original_stanza));
this.createMessage(stanza, original_stanza)
.then(msg => this.incrementUnreadMsgCounter(msg))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
if (sender !== this.get('nick')) {
// We only emit an event if it's not our own message

1062
src/converse-omemo.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -237,7 +237,7 @@
this.trigger('showReceivedOTRMessage', msg);
});
this.otr.on('io', (msg) => {
this.sendMessage(new _converse.Message({'message':msg}));
this.sendMessage({'message':msg});
});
this.otr.on('error', (msg) => {
this.trigger('showOTRError', msg);

View File

@ -48,32 +48,41 @@
events: {
'click .change-avatar': "openFileSelection",
'change input[type="file"': "updateFilePreview",
'submit form': 'onFormSubmitted'
'submit .profile-form': 'onFormSubmitted'
},
initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this);
_converse.BootstrapModal.prototype.initialize.apply(this, arguments);
_converse.emit('profileModalInitialized', this.model);
},
toHTML () {
return tpl_profile_modal(_.extend(
this.model.toJSON(),
this.model.vcard.toJSON(), {
'_': _,
'__': __,
'_converse': _converse,
'alt_avatar': __('Your avatar image'),
'heading_profile': __('Your Profile'),
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_nickname': __('Nickname'),
'label_jid': __('XMPP Address (JID)'),
'label_nickname': __('Nickname'),
'label_role': __('Role'),
'label_role_help': __('Use commas to separate multiple roles. Your roles are shown next to your name on your chat messages.'),
'label_save': __('Save'),
'label_url': __('URL'),
'alt_avatar': __('Your avatar image')
'utils': u,
'view': this
}));
},
afterRender () {
this.tabs = _.map(this.el.querySelectorAll('.nav-item'), (tab) => new bootstrap.Tab(tab));
},
openFileSelection (ev) {
ev.preventDefault();
this.el.querySelector('input[type="file"]').click();

View File

@ -23,7 +23,7 @@ if (typeof define !== 'undefined') {
"converse-muc-views",
"converse-muc-views", // Views related to MUC
"converse-notification", // HTML5 Notifications
"converse-oauth",
"converse-omemo",
"converse-ping", // XEP-0199 XMPP Ping
"converse-register", // XEP-0077 In-band registration
"converse-roomslist", // Show currently open chat rooms

View File

@ -1,4 +1,5 @@
<div class="message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
<div class="message chat-msg {{{o.type}}} {[ if (o.is_me_message) { ]} chat-msg--action {[ } ]} {{{o.extra_classes}}}"
data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}" data-encrypted="{{{o.is_encrypted}}}">
{[ if (o.type !== 'headline' && !o.is_me_message) { ]}
<canvas class="avatar chat-msg__avatar" height="36" width="36"></canvas>
{[ } ]}
@ -9,6 +10,7 @@
{[o.roles.forEach(function (role) { ]} <span class="badge badge-secondary">{{{role}}}</span> {[ }); ]}
</span>
{[ if (!o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
{[ if (o.is_encrypted) { ]}<span class="fa fa-lock"></span>{[ } ]}
</span>
{[ if (!o.is_me_message) { ]}<div class="chat-msg__body">{[ } ]}
{[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg__edit-modal"></i> {[ } ]}

View File

@ -5,54 +5,117 @@
<h5 class="modal-title" id="user-profile-modal-label">{{{o.heading_profile}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
</div>
<form class="converse-form">
<div class="modal-body">
<div class="row">
<div class="col-auto">
<a class="change-avatar" href="#">
{[ if (o.image) { ]}
<img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]}
{[ if (!o.image) { ]}
<canvas class="avatar" height="100px" width="100px"/>
{[ } ]}
</a>
<input class="hidden" name="image" type="file">
</div>
<div class="col">
<div class="form-group">
<label class="col-form-label">{{{o.label_jid}}}:</label>
<div>{{{o.jid}}}</div>
<div class="modal-body">
{[ if (o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse)) { ]}
<ul class="nav nav-pills justify-content-center">
<li role="presentation" class="nav-item">
<a class="nav-link active" id="profile-tab" href="#profile-tabpanel" aria-controls="profile-tabpanel" role="tab" data-toggle="tab">Profile</a>
</li>
<li role="presentation" class="nav-item">
<a class="nav-link" id="omemo-tab" href="#omemo-tabpanel" aria-controls="omemo-tabpanel" role="tab" data-toggle="tab">OMEMO</a>
</li>
</ul>
{[ } ]}
<div class="tab-content">
<div class="tab-pane fade show active" id="profile-tabpanel" role="tabpanel" aria-labelledby="profile-tab">
<form class="converse-form converse-form--modal profile-form" action="#">
<div class="row">
<div class="col-auto">
<a class="change-avatar" href="#">
{[ if (o.image) { ]}
<img alt="{{{o.alt_avatar}}}" class="img-thumbnail avatar align-self-center" height="100px" width="100px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]}
{[ if (!o.image) { ]}
<canvas class="avatar" height="100px" width="100px"/>
{[ } ]}
</a>
<input class="hidden" name="image" type="file">
</div>
<div class="col">
<div class="form-group">
<label class="col-form-label">{{{o.label_jid}}}:</label>
<div>{{{o.jid}}}</div>
</div>
</div>
</div>
<div class="form-group">
<label for="vcard-fullname" class="col-form-label">{{{o.label_fullname}}}:</label>
<input id="vcard-fullname" type="text" class="form-control" name="fn" value="{{{o.fullname}}}">
</div>
<div class="form-group">
<label for="vcard-nickname" class="col-form-label">{{{o.label_nickname}}}:</label>
<input id="vcard-nickname" type="text" class="form-control" name="nickname" value="{{{o.nickname}}}">
</div>
<div class="form-group">
<label for="vcard-url" class="col-form-label">{{{o.label_url}}}:</label>
<input id="vcard-url" type="url" class="form-control" name="url" value="{{{o.url}}}">
</div>
<div class="form-group">
<label for="vcard-email" class="col-form-label">{{{o.label_email}}}:</label>
<input id="vcard-email" type="email" class="form-control" name="email" value="{{{o.email}}}">
</div>
<div class="form-group">
<label for="vcard-role" class="col-form-label">{{{o.label_role}}}:</label>
<input id="vcard-role" type="text" class="form-control" name="role" value="{{{o.role}}}" aria-describedby="vcard-role-help">
<small id="vcard-role-help" class="form-text text-muted">{{{o.label_role_help}}}</small>
</div>
<hr/>
<div class="form-group">
<button type="submit" class="save-form btn btn-primary">{{{o.__('Save and close')}}}</button>
</div>
</form>
</div>
{[ if (o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse)) { ]}
<div class="tab-pane fade" id="omemo-tabpanel" role="tabpanel" aria-labelledby="omemo-tab">
<form class="converse-form fingerprint-removal">
<ul class="list-group fingerprints">
<li class="list-group-item active">{{{o.__("This device's OMEMO fingerprint")}}}</li>
<li class="list-group-item">
{[ if (o.view.current_device.get('bundle') && o.view.current_device.get('bundle').fingerprint) { ]}
<span class="fingerprint">{{{o.utils.formatFingerprint(o.view.current_device.get('bundle').fingerprint)}}}</span>
{[ } else {]}
<span class="spinner fa fa-spinner centered"/>
{[ } ]}
</li>
</ul>
{[ if (o.view.other_devices.length) { ]}
<ul class="list-group fingerprints">
<li class="list-group-item nopadding active">
<label>
<input type="checkbox" class="select-all" title="{{{o.__('Select all')}}}"
aria-label="{{{o.__('Checkbox to select fingerprints of all other OMEMO devices')}}}">
{{{o.__('Other OMEMO-enabled devices')}}}
</label>
</li>
{[ o._.forEach(o.view.other_devices, function (device) { ]}
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="fingerprint-removal-item list-group-item nopadding">
<label>
<input type="checkbox" value="{{{device.get('id')}}}"
aria-label="{{{o.__('Checkbox for selecting the following fingerprint')}}}">
<span class="fingerprint">{{{o.utils.formatFingerprint(device.get('bundle').fingerprint)}}}</span>
</label>
</li>
{[ } else {]}
<li class="fingerprint-removal-item list-group-item nopadding">
<label>
<input type="checkbox" value="{{{device.get('id')}}}"
aria-label="{{{o.__('Checkbox for selecting the following fingerprint')}}}">
<span>{{{o.__('Device without a fingerprint')}}}</span>
</label>
</li>
{[ } ]}
{[ }); ]}
</ul>
<div class="form-group">
<button type="submit" class="save-form btn btn-primary">{{{o.__('Remove checked devices and close')}}}</button>
</div>
{[ } ]}
</form>
</div>
</div>
<div class="form-group">
<label for="vcard-fullname" class="col-form-label">{{{o.label_fullname}}}:</label>
<input id="vcard-fullname" type="text" class="form-control" name="fn" value="{{{o.fullname}}}">
</div>
<div class="form-group">
<label for="vcard-nickname" class="col-form-label">{{{o.label_nickname}}}:</label>
<input id="vcard-nickname" type="text" class="form-control" name="nickname" value="{{{o.nickname}}}">
</div>
<div class="form-group">
<label for="vcard-url" class="col-form-label">{{{o.label_url}}}:</label>
<input id="vcard-url" type="url" class="form-control" name="url" value="{{{o.url}}}">
</div>
<div class="form-group">
<label for="vcard-email" class="col-form-label">{{{o.label_email}}}:</label>
<input id="vcard-email" type="email" class="form-control" name="email" value="{{{o.email}}}">
</div>
<div class="form-group">
<label for="vcard-role" class="col-form-label">{{{o.label_role}}}:</label>
<input id="vcard-role" type="text" class="form-control" name="role" value="{{{o.role}}}" aria-describedby="vcard-role-help">
<small id="vcard-role-help" class="form-text text-muted">{{{o.label_role_help}}}</small>
</div>
{[ } ]}
</div>
<div class="modal-footer">
<button type="submit" class="save-form btn btn-primary">{{{o.label_save}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.label_close}}}</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<li class="toggle-omemo fa {[ if (o.omemo_active) { ]} fa-lock {[ } else { ]} fa-unlock {[ } ]}" title="{{{o.__('Messages are being sent in plaintext')}}}"></li>

View File

@ -1,39 +1,70 @@
<div class="modal fade" id="user-profile-modal" tabindex="-1" role="dialog" aria-labelledby="user-profile-modal-label" aria-hidden="true">
<div class="modal fade" id="user-details-modal" tabindex="-1" role="dialog" aria-labelledby="user-details-modal-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="user-profile-modal-label">{{{o.display_name}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
<h5 class="modal-title" id="user-details-modal-label">{{{o.display_name}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.__('Close')}}}"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
{[ if (o.image) { ]}
<img alt="{{{o.alt_profile_image}}}"
<img alt="{{{o.__('The User\'s Profile Image')}}}"
class="img-thumbnail avatar align-self-center mb-3"
height="100" width="100" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]}
{[ if (o.fullname) { ]}
<p><label>{{{o.label_fullname}}}:</label>&nbsp;{{{o.fullname}}}</p>
<p><label>{{{o.__('Full Name')}}}:</label>&nbsp;{{{o.fullname}}}</p>
{[ } ]}
<p><label>{{{o.label_jid}}}:</label>&nbsp;{{{o.jid}}}</p>
<p><label>{{{o.__('XMPP Address')}}}:</label>&nbsp;{{{o.jid}}}</p>
{[ if (o.nickname) { ]}
<p><label>{{{o.label_nickname}}}:</label>&nbsp;{{{o.nickname}}}</p>
<p><label>{{{o.__('Nickname')}}}:</label>&nbsp;{{{o.nickname}}}</p>
{[ } ]}
{[ if (o.url) { ]}
<p><label>{{{o.label_url}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p>
<p><label>{{{o.__('URL')}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p>
{[ } ]}
{[ if (o.email) { ]}
<p><label>{{{o.label_email}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p>
<p><label>{{{o.__('Email')}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p>
{[ } ]}
{[ if (o.role) { ]}
<p><label>{{{o.label_role}}}:</label>&nbsp;{{{o.role}}}</p>
<p><label>{{{o.__('Role')}}}:</label>&nbsp;{{{o.role}}}</p>
{[ } ]}
{[ if (o._converse.pluggable.plugins['converse-omemo'].enabled(o._converse)) { ]}
<hr>
<ul class="list-group fingerprints">
<li class="list-group-item active">{{{o.__('OMEMO Fingerprints')}}}</li>
{[ if (!o.view.devicelist.devices) { ]}
<li class="list-group-item"><span class="spinner fa fa-spinner centered"/></li>
{[ } ]}
{[ if (o.view.devicelist.devices) { ]}
{[ o.view.devicelist.devices.each(function (device) { ]}
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="list-group-item">
<form class="fingerprint-trust">
<div class="btn-group btn-group-toggle">
<label class="btn btn--small {[ if (device.get('trusted') !== -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}">
<input type="radio" name="{{{device.get('id')}}}" value="1"
{[ if (device.get('trusted') !== -1) { ]} checked="checked" {[ } ]}>{{{o.__('Trusted')}}}
</label>
<label class="btn btn--small {[ if (device.get('trusted') === -1) { ]} btn-primary active {[ } else { ]} btn-secondary {[ } ]}">
<input type="radio" name="{{{device.get('id')}}}" value="-1"
{[ if (device.get('trusted') === -1) { ]} checked="checked" {[ } ]}>{{{o.__('Untrusted')}}}
</label>
</div>
<span class="fingerprint">{{{o.utils.formatFingerprint(device.get('bundle').fingerprint)}}}</span>
</form>
</li>
{[ } ]}
{[ }); ]}
{[ } ]}
</ul>
{[ } ]}
</div>
<div class="modal-footer">
{[ if (o.allow_contact_removal && o.is_roster_contact) { ]}
<button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.label_remove}}}</button>
<button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.__('Remove as contact')}}}</button>
{[ } ]}
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.label_refresh}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.label_close}}}</button>
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.__('Refresh')}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.__('Close')}}}</button>
</div>
</div>
</div>

View File

@ -6,7 +6,7 @@
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, escape, window */
/*global define, escape, window, Uint8Array */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([
@ -900,14 +900,11 @@
return text.replace(_converse.geouri_regex, replacement);
};
u.getSelectValues = function(select) {
var result = [];
var options = select && select.options;
var opt;
u.getSelectValues = function (select) {
const result = [];
const options = select && select.options;
for (var i=0, iLen=options.length; i<iLen; i++) {
opt = options[i];
const opt = options[i];
if (opt.selected) {
result.push(opt.value || opt.text);
}
@ -915,6 +912,50 @@
return result;
};
u.formatFingerprint = function (fp) {
fp = fp.replace(/^05/, '');
const arr = [];
for (let i=1; i<8; i++) {
const idx = i*8+i-1;
fp = fp.slice(0, idx) + ' ' + fp.slice(idx);
}
return fp;
};
u.arrayBufferToHex = function (ab) {
// https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex#40031979
return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join('');
};
u.arrayBufferToString = function (ab) {
const enc = new TextDecoder("utf-8");
return enc.decode(ab);
};
u.arrayBufferToBase64 = function (ab) {
return btoa((new Uint8Array(ab)).reduce((data, byte) => data + String.fromCharCode(byte), ''));
};
u.stringToArrayBuffer = function (string) {
const enc = new TextEncoder(); // always utf-8
return enc.encode(string);
};
u.base64ToArrayBuffer = function (b64) {
const binary_string = window.atob(b64),
len = binary_string.length,
bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i)
}
return bytes.buffer
};
u.getRandomInt = function (max) {
return Math.floor(Math.random() * Math.floor(max));
};
u.putCurserAtEnd = function (textarea) {
if (textarea !== document.activeElement) {
textarea.focus();

View File

@ -3,7 +3,7 @@
//
// This is the utilities module.
//
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
// Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2)
//
/*global define, escape, Jed */

View File

@ -6,8 +6,68 @@
var Strophe = converse.env.Strophe;
var moment = converse.env.moment;
var $iq = converse.env.$iq;
var mock = {};
var u = converse.env.utils;
window.libsignal = {
'SignalProtocolAddress': function (name, device_id) {
this.name = name;
this.deviceId = device_id;
},
'SessionCipher': function (storage, remote_address) {
this.remoteAddress = remote_address;
this.storage = storage;
this.encrypt = () => Promise.resolve({
'type': 1,
'body': 'c1ph3R73X7',
'registrationId': '1337'
});
this.decryptPreKeyWhisperMessage = (key_and_tag) => {
// TODO: remove the prekey
return Promise.resolve(u.stringToArrayBuffer(key_and_tag));
};
this.decryptWhisperMessage = (key_and_tag) => {
return Promise.resolve(u.stringToArrayBuffer(key_and_tag));
}
},
'SessionBuilder': function (storage, remote_address) {
this.processPreKey = function () {
return Promise.resolve();
}
},
'KeyHelper': {
'generateIdentityKeyPair': function () {
return Promise.resolve({
'pubKey': new TextEncoder('utf-8').encode('1234'),
'privKey': new TextEncoder('utf-8').encode('4321')
});
},
'generateRegistrationId': function () {
return '123456789';
},
'generatePreKey': function (keyid) {
return Promise.resolve({
'keyId': keyid,
'keyPair': {
'pubKey': new TextEncoder('utf-8').encode('1234'),
'privKey': new TextEncoder('utf-8').encode('4321')
}
});
},
'generateSignedPreKey': function (identity_keypair, keyid) {
return Promise.resolve({
'signature': new TextEncoder('utf-8').encode('11112222333344445555'),
'keyId': keyid,
'keyPair': {
'pubKey': new TextEncoder('utf-8').encode('1234'),
'privKey': new TextEncoder('utf-8').encode('4321')
}
});
}
}
};
var mock = {};
mock.view_mode = 'overlayed';
// Names from http://www.fakenamegenerator.com/

View File

@ -1,102 +0,0 @@
/*global config */
// Extra test dependencies
config.baseUrl = '../';
config.paths.jquery = "node_modules/jquery/dist/jquery";
config.paths.mock = "tests/mock";
config.paths['wait-until-promise'] = "node_modules/wait-until-promise/index";
config.paths['test-utils'] = "tests/utils";
config.paths.sinon = "node_modules/sinon/pkg/sinon";
config.paths.transcripts = "converse-logs/converse-logs";
config.paths["jasmine-core"] = "node_modules/jasmine-core/lib/jasmine-core/jasmine";
config.paths.jasmine = "node_modules/jasmine-core/lib/jasmine-core/boot";
config.paths["jasmine-console"] = "node_modules/jasmine-core/lib/console/console";
config.paths["console-reporter"] = "tests/console-reporter";
config.paths["jasmine-html"] = "node_modules/jasmine-core/lib/jasmine-core/jasmine-html";
config.paths.converse = "builds/converse";
config.paths.utils = "builds/utils";
config.paths["converse-bookmarks"] = "builds/converse-bookmarks";
config.paths["converse-chatboxes"] = "builds/converse-chatboxes";
config.paths["converse-chatview"] = "builds/converse-chatview";
config.paths["converse-controlbox"] = "builds/converse-controlbox";
config.paths["converse-core"] = "builds/converse-core";
config.paths["converse-disco"] = "builds/converse-disco";
config.paths["converse-dragresize"] = "builds/converse-dragresize";
config.paths["converse-headline"] = "builds/converse-headline";
config.paths["converse-http-file-upload"]="builds/converse-http-file-upload";
config.paths["converse-fullscreen"] = "builds/converse-fullscreen";
config.paths["converse-mam"] = "builds/converse-mam";
config.paths["converse-minimize"] = "builds/converse-minimize";
config.paths["converse-muc"] = "builds/converse-muc";
config.paths["converse-muc-embedded"] = "builds/converse-muc-embedded";
config.paths["converse-notification"] = "builds/converse-notification";
config.paths["converse-otr"] = "builds/converse-otr";
config.paths["converse-ping"] = "builds/converse-ping";
config.paths["converse-profile"] = "builds/converse-profile";
config.paths["converse-register"] = "builds/converse-register";
config.paths["converse-roomslist"] = "builds/converse-roomslist";
config.paths["converse-rosterview"] = "builds/converse-rosterview";
config.paths["converse-singleton"] = "builds/converse-singleton";
config.paths["converse-vcard"] = "builds/converse-vcard";
config.shim.jasmine = {
exports: 'window.jasmineRequire'
};
config.shim['jasmine-html'] = {
deps: ['jasmine-core'],
exports: 'window.jasmineRequire'
};
config.shim['jasmine-console'] = {
deps: ['jasmine-core'],
exports: 'window.jasmineRequire'
};
config.shim.jasmine = {
deps: ['jasmine-core', 'jasmine-html', 'jasmine-console'],
exports: 'window.jasmine'
};
require.config(config);
var specs = [
"jasmine",
//"spec/transcripts",
"spec/profiling",
"spec/utils",
"spec/converse",
"spec/bookmarks",
"spec/roomslist",
"spec/headline",
"spec/disco",
"spec/protocol",
"spec/presence",
"spec/eventemitter",
"spec/ping",
"spec/xmppstatus",
"spec/mam",
"spec/otr",
"spec/controlbox",
"spec/roster",
"spec/chatbox",
"spec/chatroom",
"spec/minchats",
"spec/notification",
"spec/register"
];
require(['console-reporter', 'mock', 'sinon', 'wait-until-promise', 'pluggable'],
function(ConsoleReporter, mock, sinon, waitUntilPromise, pluggable) {
window.sinon = sinon;
waitUntilPromise.setPromiseImplementation(window.Promise);
window.waitUntilPromise = waitUntilPromise.default;
window.localStorage.clear();
window.sessionStorage.clear();
// Load the specs
require(specs, function (jasmine) {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.addReporter(new ConsoleReporter());
window.onload();
});
});

View File

@ -179,6 +179,7 @@ require.config(config);
var specs = [
"jasmine",
//"spec/transcripts",
//"spec/otr",
"spec/spoilers",
"spec/profiling",
"spec/utils",
@ -194,7 +195,7 @@ var specs = [
"spec/push",
"spec/xmppstatus",
"spec/mam",
// "spec/otr",
"spec/omemo",
"spec/controlbox",
"spec/roster",
"spec/chatbox",

View File

@ -100,7 +100,7 @@
utils.openChatBoxFor = function (_converse, jid) {
_converse.roster.get(jid).trigger("open");
return utils.waitUntil(() => _converse.chatboxviews.get(jid));
return utils.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
utils.openChatRoomViaModal = function (_converse, jid, nick='') {