├── .gitignore ├── README.md ├── demos.html ├── fonts ├── .DS_Store └── icomoon │ ├── Read Me.txt │ ├── demo-files │ ├── demo.css │ └── demo.js │ ├── demo.html │ ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff │ ├── selection.json │ └── style.css ├── images ├── blank-white.jpg ├── file │ ├── default-file-icon.jpg │ ├── default-link-icon.jpg │ └── default_file_icon.jpg ├── mesibo-bg-white.png ├── mesibo-logo.png ├── paper-plane.png └── profile │ ├── default-group-icon.jpg │ └── default-profile-icon.jpg ├── index.html ├── login ├── login.css └── login.js ├── mesibo ├── .files.js.swp ├── calls.js ├── config.js ├── files.js ├── login.js ├── recorder.js └── utils.js ├── messenger.html ├── scripts ├── controller.js └── ui.js └── styles ├── messenger.css ├── popup.css └── popupdesign.css /.gitignore: -------------------------------------------------------------------------------- 1 | backup.sh 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Messenger-Javascript 2 | This repository contains the source code for the Mesibo Sample Web apps built using Mesibo Javascript API. 3 | 4 | - **messenger** - A Whatsapp like messaging app that loads a list of users on the left and messages on the right 5 | 6 | > Please note that this is currently **under development** and will be continuously updated. 7 | 8 | ## Features: 9 | - One-to-One Messaging, Voice and Video Call 10 | - Group Messaging 11 | - Read receipts 12 | - Forward, Delete & Resend 13 | - Sending Files 14 | - Record and Send live audio, video & picture 15 | - Link Preview 16 | - Multi-Device Synchronization(*Supported if you are using mesibo On-Premise*) 17 | - Multi-Tab popup 18 | 19 | ### Features under implementation 20 | - Date header in the message area 21 | 22 | ## Instructions 23 | All these demos require a mesibo user token which you can configure in `config.js` or use login screen to generate it. 24 | 25 | If you do not know what is mesibo user token, refer to the [Get-Started Guide](https://mesibo.com/documentation/tutorials/get-started) to learn about the basics of mesibo before continuing further. 26 | 27 | Refer to the `config.js` for configuration and instructions. 28 | 29 | ### Login 30 | The login code is completely independent of demos which makes it easier for you to rebrand. Login code generates token and saves into local storage and launches messenger (you can change this). The messenger/popup code reads token from either `config.js` (if configured) or local storage. 31 | 32 | To login, in the login screen provide the phone number along with country code starting with `+` For Example, If your country code is `1 (United States)` and your ten-digit phone number is `XXXXXXXXXX`, enter your phone number as `+1XXXXXXXXXX` (without any spaces or special characters in between) 33 | 34 | You need to log in to your mesibo account to generate OTP. 35 | 36 | ## Support 37 | Refer to following links before raising any support requests 38 | 39 | - https://mesibo.com/documentation/faq/support/#what-information-should-i-provide-when-requesting-technical-support 40 | 41 | - https://mesibo.com/documentation/faq/support/#can-you-help-with-messenger-whatsapp-clone-demo-and-ui-modules 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mesibo Javascript Sample Apps 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |

Mesibo Javascript Sample Apps

16 | 17 | 18 | The following sample apps are built using Mesibo Javascript SDK. 19 | 20 | These sample apps are meant to be a starting point to help you build applications using mesibo. 21 | Before you go ahead, it is recommended that you read the Get Started tutorial and try Mesibo Javascript Basic Demo . 22 | 23 |
24 | To use any of the sample apps below, you need an access token. You can get this token by creating a user in the console or using backend API. Once you get the the token you need to edit config.js, set the token and the app-id that you used to generate the token.
25 |
26 |
27 |
28 |
29 |
30 |
Messenger Demo
31 |

This is a fully featured Whatsapp like app which displays a list of users on the left and messages on the right.

32 | Open Messenger 33 |
34 |
35 |
36 |
37 |
Popup demo
38 |

This is a simple chat popup app. You can send messages and make calls to a single user.

39 | Open Popup 40 |
41 |
42 |
43 |
44 |
Multi-tab Popup
45 |

This is a multi-tab chat popup app. This chat popup app can be simultaneously opened across multiple tabs. All the tabs share a single connection to mesibo.

46 | Open Multitab Popup 47 |
48 |
49 |
50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/fonts/.DS_Store -------------------------------------------------------------------------------- /fonts/icomoon/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /fonts/icomoon/demo-files/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | font-family: sans-serif; 5 | font-size: 1em; 6 | line-height: 1.5; 7 | color: #555; 8 | background: #fff; 9 | } 10 | h1 { 11 | font-size: 1.5em; 12 | font-weight: normal; 13 | } 14 | small { 15 | font-size: .66666667em; 16 | } 17 | a { 18 | color: #e74c3c; 19 | text-decoration: none; 20 | } 21 | a:hover, a:focus { 22 | box-shadow: 0 1px #e74c3c; 23 | } 24 | .bshadow0, input { 25 | box-shadow: inset 0 -2px #e7e7e7; 26 | } 27 | input:hover { 28 | box-shadow: inset 0 -2px #ccc; 29 | } 30 | input, fieldset { 31 | font-family: sans-serif; 32 | font-size: 1em; 33 | margin: 0; 34 | padding: 0; 35 | border: 0; 36 | } 37 | input { 38 | color: inherit; 39 | line-height: 1.5; 40 | height: 1.5em; 41 | padding: .25em 0; 42 | } 43 | input:focus { 44 | outline: none; 45 | box-shadow: inset 0 -2px #449fdb; 46 | } 47 | .glyph { 48 | font-size: 16px; 49 | width: 15em; 50 | padding-bottom: 1em; 51 | margin-right: 4em; 52 | margin-bottom: 1em; 53 | float: left; 54 | overflow: hidden; 55 | } 56 | .liga { 57 | width: 80%; 58 | width: calc(100% - 2.5em); 59 | } 60 | .talign-right { 61 | text-align: right; 62 | } 63 | .talign-center { 64 | text-align: center; 65 | } 66 | .bgc1 { 67 | background: #f1f1f1; 68 | } 69 | .fgc1 { 70 | color: #999; 71 | } 72 | .fgc0 { 73 | color: #000; 74 | } 75 | p { 76 | margin-top: 1em; 77 | margin-bottom: 1em; 78 | } 79 | .mvm { 80 | margin-top: .75em; 81 | margin-bottom: .75em; 82 | } 83 | .mtn { 84 | margin-top: 0; 85 | } 86 | .mtl, .mal { 87 | margin-top: 1.5em; 88 | } 89 | .mbl, .mal { 90 | margin-bottom: 1.5em; 91 | } 92 | .mal, .mhl { 93 | margin-left: 1.5em; 94 | margin-right: 1.5em; 95 | } 96 | .mhmm { 97 | margin-left: 1em; 98 | margin-right: 1em; 99 | } 100 | .mls { 101 | margin-left: .25em; 102 | } 103 | .ptl { 104 | padding-top: 1.5em; 105 | } 106 | .pbs, .pvs { 107 | padding-bottom: .25em; 108 | } 109 | .pvs, .pts { 110 | padding-top: .25em; 111 | } 112 | .unit { 113 | float: left; 114 | } 115 | .unitRight { 116 | float: right; 117 | } 118 | .size1of2 { 119 | width: 50%; 120 | } 121 | .size1of1 { 122 | width: 100%; 123 | } 124 | .clearfix:before, .clearfix:after { 125 | content: " "; 126 | display: table; 127 | } 128 | .clearfix:after { 129 | clear: both; 130 | } 131 | .hidden-true { 132 | display: none; 133 | } 134 | .textbox0 { 135 | width: 3em; 136 | background: #f1f1f1; 137 | padding: .25em .5em; 138 | line-height: 1.5; 139 | height: 1.5em; 140 | } 141 | #testDrive { 142 | display: block; 143 | padding-top: 24px; 144 | line-height: 1.5; 145 | } 146 | .fs0 { 147 | font-size: 16px; 148 | } 149 | .fs1 { 150 | font-size: 28px; 151 | } 152 | .fs2 { 153 | font-size: 24px; 154 | } 155 | 156 | -------------------------------------------------------------------------------- /fonts/icomoon/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!('boxShadow' in document.body.style)) { 2 | document.body.setAttribute('class', 'noBoxShadow'); 3 | } 4 | 5 | document.body.addEventListener("click", function(e) { 6 | var target = e.target; 7 | if (target.tagName === "INPUT" && 8 | target.getAttribute('class').indexOf('liga') === -1) { 9 | target.select(); 10 | } 11 | }); 12 | 13 | (function() { 14 | var fontSize = document.getElementById('fontSize'), 15 | testDrive = document.getElementById('testDrive'), 16 | testText = document.getElementById('testText'); 17 | function updateTest() { 18 | testDrive.innerHTML = testText.value || String.fromCharCode(160); 19 | if (window.icomoonLiga) { 20 | window.icomoonLiga(testDrive); 21 | } 22 | } 23 | function updateSize() { 24 | testDrive.style.fontSize = fontSize.value + 'px'; 25 | } 26 | fontSize.addEventListener('change', updateSize, false); 27 | testText.addEventListener('input', updateTest, false); 28 | testText.addEventListener('change', updateTest, false); 29 | updateSize(); 30 | }()); 31 | -------------------------------------------------------------------------------- /fonts/icomoon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/fonts/icomoon/fonts/icomoon.eot -------------------------------------------------------------------------------- /fonts/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/fonts/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /fonts/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/fonts/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /images/blank-white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/blank-white.jpg -------------------------------------------------------------------------------- /images/file/default-file-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/file/default-file-icon.jpg -------------------------------------------------------------------------------- /images/file/default-link-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/file/default-link-icon.jpg -------------------------------------------------------------------------------- /images/file/default_file_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/file/default_file_icon.jpg -------------------------------------------------------------------------------- /images/mesibo-bg-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/mesibo-bg-white.png -------------------------------------------------------------------------------- /images/mesibo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/mesibo-logo.png -------------------------------------------------------------------------------- /images/paper-plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/paper-plane.png -------------------------------------------------------------------------------- /images/profile/default-group-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/profile/default-group-icon.jpg -------------------------------------------------------------------------------- /images/profile/default-profile-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/images/profile/default-profile-icon.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | mesibo open-source messenger demo 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 35 |
36 |
37 |
38 |
39 |
40 |

Sign In to messenger demo

41 |

Note: We will NOT send OTP. You will need to login to your mesibo account to generate OTPs

42 |
43 |
44 | 45 | 46 | 47 |
48 |
49 | 50 | 51 | 52 |
53 | 54 |
55 | 57 | Start Again 58 |
59 | 60 | 61 | 62 | You can also download open source mobile apps 63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 |
77 | 78 |
79 | 80 |
81 |
82 |
83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /login/login.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Roboto", sans-serif; 3 | background-color: #f8fafb; } 4 | 5 | p { 6 | color: #737373; 7 | font-weight: 300; 8 | } 9 | 10 | h1, h2, h3, h4, h5, h6, 11 | .h1, .h2, .h3, .h4, .h5, .h6 { 12 | font-family: "Roboto", sans-serif; } 13 | 14 | a { 15 | -webkit-transition: .3s all ease; 16 | -o-transition: .3s all ease; 17 | transition: .3s all ease; 18 | text-decoration: none !important; 19 | color: #00868b !important; 20 | } 21 | a:hover { 22 | text-decoration: none !important; } 23 | 24 | .content { 25 | padding: 7rem 0; } 26 | 27 | h2 { 28 | font-size: 20px; } 29 | 30 | .form-block { 31 | background: #fff; 32 | padding: 60px; 33 | -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1); 34 | box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.1); } 35 | @media (max-width: 991.98px) { 36 | .form-block { 37 | padding: 30px; } } 38 | 39 | @media (max-width: 991.98px) { 40 | .content .bg { 41 | height: 500px; } } 42 | 43 | .content .contents, .content .bg { 44 | width: 50%; } 45 | @media (max-width: 1199.98px) { 46 | .content .contents, .content .bg { 47 | width: 100%; } } 48 | .content .contents .form-group, .content .bg .form-group { 49 | position: relative; } 50 | .content .contents .form-group label, .content .bg .form-group label { 51 | position: absolute; 52 | top: 50%; 53 | -webkit-transform: translateY(-50%); 54 | -ms-transform: translateY(-50%); 55 | transform: translateY(-50%); 56 | -webkit-transition: .3s all ease; 57 | -o-transition: .3s all ease; 58 | transition: .3s all ease; } 59 | .content .contents .form-group input, .content .bg .form-group input { 60 | background: transparent; 61 | border-bottom: 1px solid #ccc; } 62 | .content .contents .form-group.first, .content .bg .form-group.first { 63 | border-top-left-radius: 7px; 64 | border-top-right-radius: 7px; } 65 | .content .contents .form-group.last, .content .bg .form-group.last { 66 | border-bottom-left-radius: 7px; 67 | border-bottom-right-radius: 7px; } 68 | .content .contents .form-group label, .content .bg .form-group label { 69 | font-size: 12px; 70 | display: block; 71 | margin-bottom: 0; 72 | color: #b3b3b3; } 73 | .content .contents .form-group.focus, .content .bg .form-group.focus { 74 | background: #fff; } 75 | .content .contents .form-group.field--not-empty label, .content .bg .form-group.field--not-empty label { 76 | margin-top: -25px; } 77 | .content .contents .form-control, .content .bg .form-control { 78 | border: none; 79 | padding: 0; 80 | font-size: 20px; 81 | border-radius: 0; } 82 | .content .contents .form-control:active, .content .contents .form-control:focus, .content .bg .form-control:active, .content .bg .form-control:focus { 83 | outline: none; 84 | -webkit-box-shadow: none; 85 | box-shadow: none; } 86 | 87 | .content .bg { 88 | background-size: cover; 89 | background-position: center; } 90 | 91 | .content a { 92 | color: #888; 93 | text-decoration: underline; } 94 | 95 | .content .btn { 96 | height: 54px; 97 | padding-left: 30px; 98 | padding-right: 30px; } 99 | 100 | .content .forgot-pass { 101 | position: relative; 102 | top: 2px; 103 | font-size: 14px; } 104 | 105 | .content .btn-pill { 106 | border-radius: 30px; } 107 | 108 | .social-login a { 109 | text-decoration: none; 110 | position: relative; 111 | text-align: center; 112 | color: #fff; 113 | margin-bottom: 10px; 114 | width: 50px; 115 | height: 50px; 116 | border-radius: 50%; 117 | display: inline-block; } 118 | .social-login a span { 119 | position: absolute; 120 | top: 50%; 121 | left: 50%; 122 | -webkit-transform: translate(-50%, -50%); 123 | -ms-transform: translate(-50%, -50%); 124 | transform: translate(-50%, -50%); } 125 | .social-login a:hover { 126 | color: #fff; } 127 | .social-login a.facebook { 128 | background: #3b5998; } 129 | .social-login a.facebook:hover { 130 | background: #344e86; } 131 | .social-login a.twitter { 132 | background: #1da1f2; } 133 | .social-login a.twitter:hover { 134 | background: #0d95e8; } 135 | .social-login a.google { 136 | background: #ea4335; } 137 | .social-login a.google:hover { 138 | background: #e82e1e; } 139 | 140 | .control { 141 | display: block; 142 | position: relative; 143 | padding-left: 30px; 144 | margin-bottom: 15px; 145 | cursor: pointer; 146 | font-size: 14px; } 147 | .control .caption { 148 | position: relative; 149 | top: .2rem; 150 | color: #888; } 151 | 152 | .control input { 153 | position: absolute; 154 | z-index: -1; 155 | opacity: 0; } 156 | 157 | .control__indicator { 158 | position: absolute; 159 | top: 2px; 160 | left: 0; 161 | height: 20px; 162 | width: 20px; 163 | background: #e6e6e6; 164 | border-radius: 4px; } 165 | 166 | .control--radio .control__indicator { 167 | border-radius: 50%; } 168 | 169 | .control:hover input ~ .control__indicator, 170 | .control input:focus ~ .control__indicator { 171 | background: #ccc; } 172 | 173 | .control input:checked ~ .control__indicator { 174 | background: #38d39f; } 175 | 176 | .control:hover input:not([disabled]):checked ~ .control__indicator, 177 | .control input:checked:focus ~ .control__indicator { 178 | background: #4dd8a9; } 179 | 180 | .control input:disabled ~ .control__indicator { 181 | background: #e6e6e6; 182 | opacity: 0.9; 183 | pointer-events: none; } 184 | 185 | .control__indicator:after { 186 | font-family: 'icomoon'; 187 | content: '\e5ca'; 188 | position: absolute; 189 | display: none; 190 | font-size: 16px; 191 | -webkit-transition: .3s all ease; 192 | -o-transition: .3s all ease; 193 | transition: .3s all ease; } 194 | 195 | .control input:checked ~ .control__indicator:after { 196 | display: block; 197 | color: #fff; } 198 | 199 | .control--checkbox .control__indicator:after { 200 | top: 50%; 201 | left: 50%; 202 | margin-top: -1px; 203 | -webkit-transform: translate(-50%, -50%); 204 | -ms-transform: translate(-50%, -50%); 205 | transform: translate(-50%, -50%); } 206 | 207 | .control--checkbox input:disabled ~ .control__indicator:after { 208 | border-color: #7b7b7b; } 209 | 210 | .control--checkbox input:disabled:checked ~ .control__indicator { 211 | background-color: #7e0cf5; 212 | opacity: .2; } 213 | 214 | .my-primary{ 215 | color: lightYellow ; 216 | background-color: #00868b; 217 | } 218 | 219 | .my-primary{ 220 | background-color: #00868b; 221 | } 222 | 223 | .my-primary:focus, .my-primary:hover{ 224 | background-color: #00869b; 225 | } 226 | 227 | -------------------------------------------------------------------------------- /login/login.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 'use strict'; 3 | 4 | 5 | console.log('login init'); 6 | $('.form-control').on('input', function() { 7 | var $field = $(this).closest('.form-group'); 8 | if (this.value) { 9 | $field.addClass('field--not-empty'); 10 | } else { 11 | $field.removeClass('field--not-empty'); 12 | } 13 | }); 14 | 15 | }); 16 | 17 | function redirect_messenger() { 18 | window.location.replace("messenger.html"); 19 | } 20 | 21 | function login_init() { 22 | console.log('start login'); 23 | var token = getLoginToken(); 24 | if(token && token.length > 16) { 25 | redirect_messenger(); 26 | return; 27 | } 28 | var mesibo = Mesibo.getInstance(); 29 | 30 | document.getElementById("otpdiv").style.display = "none"; 31 | document.getElementById("otp").value = ''; 32 | document.getElementById('phone').readOnly = false; 33 | document.getElementById("phone").value = ''; 34 | _displayLoginError(null); 35 | } 36 | 37 | function login_start() { 38 | var phone = document.getElementById("phone").value; 39 | if(!phone || phone.length < 10) { 40 | _displayLoginError("Invalid Phone Number"); 41 | return ""; 42 | } 43 | 44 | _displayLoginError(null); 45 | if(phone[0] == '+'){ 46 | phone = phone.substr(1); 47 | } 48 | 49 | var otp = document.getElementById("otp").value; 50 | 51 | var p = {}; 52 | p['op'] = 'login'; 53 | p['appid'] = MESIBO_APP_ID; 54 | p['phone'] = phone; 55 | 56 | if(otp && otp.length > 4){ 57 | p['otp'] = otp; 58 | console.log("gen with otp"); 59 | } 60 | 61 | 62 | var http = Mesibo.getInstance().createhttpRequest(); 63 | http.setUrl(MESSENGER_API_URL); 64 | http.setPostData(p, true); 65 | http.send(null, function(cbdata, response) { 66 | console.log(response); 67 | var resp = JSON.parse(response); 68 | if(resp.result != "OK") { 69 | console.log(resp); 70 | _displayLoginError(resp.result); 71 | return; 72 | } 73 | 74 | document.getElementById('phone').readOnly = true; 75 | document.getElementById("otpdiv").style.display = "block"; 76 | document.getElementById("otp").value = ""; 77 | 78 | var token = resp.token; 79 | if(token && token.length > 16){ 80 | console.log("Login Successfull"); 81 | 82 | document.getElementById("phone").innerHTML = null; 83 | document.getElementById("otp").innerHTML = null; 84 | 85 | saveLoginToken(token); 86 | redirect_messenger(); 87 | } 88 | 89 | }); 90 | } 91 | 92 | function _displayLoginError(error) { 93 | document.getElementById("errmsg").style.display = error?"block":"none"; 94 | if(error) 95 | document.getElementById("errmsg").value = error; 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /mesibo/.files.js.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mesibo/messenger-javascript/2f54fb0234f7c2935c506c0204519e93b6323b88/mesibo/.files.js.swp -------------------------------------------------------------------------------- /mesibo/calls.js: -------------------------------------------------------------------------------- 1 | // calls.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | 46 | function MesiboCall(s) { 47 | this.scope = s; 48 | this.api = {}; 49 | this.init() 50 | 51 | } 52 | 53 | MesiboCall.prototype.init = function(){ 54 | this.api = this.scope.getMesibo(); 55 | if(!isValid(this.api)){ 56 | MesiboLog("Invalid Mesibo Instance"); 57 | return -1; 58 | } 59 | 60 | return this; 61 | } 62 | 63 | MesiboCall.prototype.videoCall = function() { 64 | 65 | // Setup UI elements for video call 66 | this.scope.showVideoCall(); 67 | this.api.setupVideoCall("localVideo", "remoteVideo", true); 68 | 69 | //Video Call API 70 | this.api.call(this.scope.selected_user.getAddress()); 71 | 72 | } 73 | 74 | MesiboCall.prototype.voiceCall = function() { 75 | MesiboLog('voiceCall'); 76 | 77 | // Setup UI elements for audio call 78 | this.scope.showVoiceCall(); 79 | this.api.setupVoiceCall("audioPlayer"); 80 | 81 | //Voice Call API 82 | this.api.call(this.scope.selected_user.getAddress()); 83 | } 84 | 85 | MesiboCall.prototype.answer = function() { 86 | 87 | //Common modal popup notification for a call. Select the required modal to be displayed 88 | if (this.scope.is_video_call) 89 | this.video_answer(); 90 | else 91 | this.voice_answer(); 92 | 93 | } 94 | 95 | /** Control Modal View **/ 96 | MesiboCall.prototype.hangup = function() { 97 | this.scope.hangupCall(); 98 | this.api.hangup(0); 99 | } 100 | 101 | MesiboCall.prototype.video_answer = function() { 102 | this.scope.showVideoCall(); 103 | this.api.answer(true); 104 | } 105 | 106 | MesiboCall.prototype.video_hangup = function() { 107 | this.scope.hangupVideoCall(); 108 | this.api.hangup(0); 109 | } 110 | 111 | MesiboCall.prototype.voice_answer = function() { 112 | this.scope.showVoiceCall(); 113 | this.api.answer(true); 114 | } 115 | 116 | MesiboCall.prototype.voice_hangup = function() { 117 | this.scope.hangupAudioCall(); 118 | this.api.hangup(0); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /mesibo/config.js: -------------------------------------------------------------------------------- 1 | // config.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | /* Refer following tutorial and API documentation to know how to create a user token 46 | * https://mesibo.com/documentation/tutorials/first-app/ 47 | * 48 | * Note, that if you are using logging in with your phone, Mesibo will generate the token. 49 | * In that case, there is no need to configure token here 50 | * 51 | */ 52 | var MESIBO_ACCESS_TOKEN = ""; 53 | 54 | /* App ID used to create a user token. */ 55 | var MESIBO_APP_ID = "com.mesibo.jsdemo"; 56 | 57 | /* If you are hosting Mesibo backend on your own server, change this accordingly. 58 | * Refer https://github.com/mesibo/messenger-app-backend 59 | */ 60 | const MESSENGER_API_URL = "https://messenger.mesibo.com"; 61 | 62 | /* Default images */ 63 | const MESIBO_DEFAULT_PROFILE_IMAGE = "images/profile/default-profile-icon.jpg"; 64 | const MESIBO_DEFAULT_GROUP_IMAGE = "images/profile/default-group-icon.jpg"; 65 | 66 | /************************ Messenger Config Start *****************************/ 67 | 68 | /* Toggle for synchronizing messages 69 | * See https://mesibo.com/documentation/tutorials/get-started/synchronization/ 70 | */ 71 | var isMessageSync = true; 72 | 73 | /*Optional link preview*/ 74 | const isLinkPreview = false; //Set to false if link preview not required 75 | 76 | /************************ Messenger Config End *****************************/ 77 | 78 | /************************ Popup Config Start *****************************/ 79 | 80 | /* A destination where the popup demo app will send message or make calls */ 81 | const POPUP_DESTINATION_USER = "" 82 | 83 | /************************ Popup Config End *****************************/ 84 | 85 | 86 | /* Debug Mode Configuration */ 87 | isDebug = true ;// toggle this to turn on / off for global control 88 | if (isDebug) var MesiboLog = console.log.bind(window.console); 89 | else var MesiboLog = function() {} 90 | 91 | var ErrorLog = console.log.bind(window.console); 92 | 93 | function saveLoginToken(token){ 94 | localStorage.setItem("MESIBO_MESSENGER_TOKEN", token); 95 | return 0; 96 | } 97 | 98 | function deleteTokenInStorage(){ 99 | localStorage.removeItem("MESIBO_MESSENGER_TOKEN"); 100 | } 101 | 102 | function hasHardCodedLoginToken(){ 103 | return(MESIBO_ACCESS_TOKEN && MESIBO_ACCESS_TOKEN.length > 16); 104 | } 105 | 106 | function getLoginToken(){ 107 | if(MESIBO_ACCESS_TOKEN && MESIBO_ACCESS_TOKEN.length > 16) 108 | return MESIBO_ACCESS_TOKEN; 109 | 110 | var token = localStorage.getItem("MESIBO_MESSENGER_TOKEN"); 111 | if(token && token.length > 16) return token; 112 | 113 | return null; 114 | } 115 | 116 | function showAuthFailAlert() { 117 | alert("Invalid Token\n\nEnsure that the token was generated using appid: " + MESIBO_APP_ID); 118 | } 119 | -------------------------------------------------------------------------------- /mesibo/files.js: -------------------------------------------------------------------------------- 1 | // files.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | function MessengerFile(s) { 46 | this.scope = s; 47 | this.api ={}; 48 | this.init(); 49 | } 50 | 51 | MessengerFile.prototype.init = function(){ 52 | this.api = this.scope.getMesibo(); 53 | } 54 | 55 | 56 | MessengerFile.prototype.dataURItoBlob = function(dataURI) { 57 | // convert base64 to raw binary data held in a string 58 | // doesn't handle URLEncoded DataURIs 59 | var byteString = atob(dataURI.split(',')[1]); 60 | 61 | // separate out the mime component 62 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] 63 | 64 | // write the bytes of the string to an ArrayBuffer 65 | var ab = new ArrayBuffer(byteString.length); 66 | 67 | // create a view into the buffer 68 | var ia = new Uint8Array(ab); 69 | 70 | // set the bytes of the buffer to the correct values 71 | for (var i = 0; i < byteString.length; i++) { 72 | ia[i] = byteString.charCodeAt(i); 73 | } 74 | 75 | // write the ArrayBuffer to a blob, and you're done 76 | var blob = new Blob([ab], { 77 | type: mimeString 78 | }); 79 | return blob; 80 | 81 | } 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /mesibo/login.js: -------------------------------------------------------------------------------- 1 | //login.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | function _getPhoneNumber(){ 46 | var phone = document.getElementById("phone").value; 47 | if(!phone) 48 | return ""; 49 | 50 | //xxx:Validate Phone Number 51 | if(phone[0] == '+'){ 52 | phone = phone.substr(1); //Strip + 53 | } 54 | 55 | return phone; 56 | } 57 | 58 | function _getVerificationCode(){ 59 | var code = document.getElementById("otp").value; 60 | if(!isValidString(code)) 61 | return ""; 62 | 63 | //xxx:Validate code 64 | return code; 65 | } 66 | 67 | function _getAppId(){ 68 | if(!isValidString(MESIBO_APP_ID)) 69 | return ""; 70 | 71 | return MESIBO_APP_ID; 72 | } 73 | 74 | function getMesiboDemoAppToken(api) { 75 | var p = {}; 76 | p['op'] = 'login'; 77 | p['appid'] = _getAppId(); 78 | p['phone'] = _getPhoneNumber(); 79 | var otp = _getVerificationCode(); 80 | 81 | if(isValidString(otp)){ 82 | p['otp'] = otp; 83 | //Login with OTP 84 | console.log("gen with otp"); 85 | } 86 | else if(isValidString(phone)){ 87 | //Register Phone to get OTP 88 | document.getElementById('phone').readOnly = true; 89 | document.getElementById("otp-input").style.display = "block"; 90 | document.getElementById("otp").innerHTML = ""; 91 | } 92 | 93 | var http = api.createhttpRequest(); 94 | http.setUrl(MESSENGER_API_URL); 95 | http.setPostData(p, true); 96 | http.send(null, function(cbdata, response) { 97 | console.log(response); 98 | var resp = JSON.parse(response); 99 | if(resp.result != "OK") { 100 | console.log(resp); 101 | _displayLoginError(resp.result); 102 | return; 103 | } 104 | 105 | var token = resp.token; 106 | if(isValidString(token)){ 107 | console.log("Login Successfull"); 108 | 109 | document.getElementById("phone").innerHTML = null; 110 | document.getElementById("otp").innerHTML = null; 111 | 112 | $('#ModalLoginForm').modal('hide'); 113 | MESIBO_ACCESS_TOKEN = token; 114 | setTokenInStorage(token); 115 | 116 | //Launch Messenger Application 117 | launchMessenger(); 118 | } 119 | 120 | }); 121 | } 122 | 123 | function _displayLoginError(error) { 124 | alert("Login Error: " + error + "\n Please ensure you have entered a valid phone number & otp"); 125 | } 126 | 127 | function loadLoginWindow(){ 128 | $('#ModalLoginForm').modal({backdrop: 'static', keyboard: false}); 129 | document.getElementById("otp-input").style.display = "none"; 130 | document.getElementById("otp").innerHTML = null; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /mesibo/recorder.js: -------------------------------------------------------------------------------- 1 | // recorder.js 2 | 3 | /** Copyright (c) 2022 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * Recorder can be used to capture and send live media(captured from WebCam etc) 43 | * Example: Audio Clip, Video Clip, Picture, etc 44 | * 45 | * Uses: 46 | * WebRTC https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API 47 | * Media Recorder https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder 48 | * 49 | * Audio Recording uses code from the Web Dictaphone demo. 50 | * Refer to the source code at https://github.com/mdn/web-dictaphone/ 51 | * 52 | * Taking still photos with WebRTC 53 | * https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Taking_still_photos 54 | * 55 | */ 56 | 57 | function MesiboRecorder(s, type) { 58 | this.scope = s; 59 | this.type = type; 60 | 61 | this.record = document.querySelector('.record'); 62 | this.stop = document.querySelector('.stop'); 63 | this.mediaClips = document.querySelector('.sound-clips'); 64 | this.canvas = document.querySelector('.visualizer'); 65 | this.camera = document.querySelector('.camera'); 66 | this.photo = document.getElementById('photo_button'); 67 | this.stream = null; 68 | this.videoRecorder = false; 69 | 70 | this.canvasCtx = this.canvas.getContext("2d"); 71 | 72 | window.rCtx = this; 73 | 74 | if(type == 'audio') 75 | this.audioRecorder(); 76 | else if(type == 'picture') 77 | this.pictureRecorder(); 78 | else if(type == 'video') { 79 | this.pictureRecorder(); 80 | this.videoRecorder = true; 81 | } 82 | } 83 | 84 | MesiboRecorder.prototype.audioRecorder = function(){ 85 | 86 | // disable stop button while not recording 87 | this.stop.disabled = true; 88 | 89 | // visualiser setup - create web audio api context and canvas 90 | this.audioCtx = null; 91 | 92 | //Initially hide clips area and show only visualizer, record & stop buttons 93 | this.canvas.style.display = "initial"; 94 | 95 | this.record.style.display = "inline-block"; 96 | this.stop.style.display = "inline-block"; 97 | 98 | this.mediaClips.style.display = "none"; 99 | this.record.style.background = ""; 100 | this.record.style.color = ""; 101 | 102 | this.camera.style.display = "none"; 103 | 104 | } 105 | 106 | MesiboRecorder.prototype.pictureRecorder = function(){ 107 | 108 | this.camera.style.display = "block"; 109 | 110 | this.canvas.style.display = "none"; 111 | this.photo.disabled = false; 112 | // disable stop button while not recording 113 | this.stop.disabled = true; 114 | this.record.style.display = "inline-block"; 115 | this.stop.style.display = "inline-block"; 116 | 117 | this.mediaClips.style.display = "none"; 118 | 119 | 120 | this.video = document.getElementById("capture-video"); 121 | this.photo = document.getElementById('captured-photo'); 122 | 123 | this.video.style.display = "inline-block"; 124 | this.photo.style.display = "none"; 125 | 126 | document.getElementById("buttons").style.display = "block"; 127 | document.getElementById("recording_area").style.display = "block"; 128 | 129 | } 130 | 131 | //main block for doing the video and picture recording 132 | MesiboRecorder.prototype.initPictureRecording = function(){ 133 | MesiboLog("initPictureRecording called", this); 134 | 135 | let rCtx = window.rCtx; 136 | 137 | if(!rCtx.stream){ 138 | navigator.mediaDevices.getUserMedia({ video: true, audio: this.videoRecorder }) 139 | .then(function(stream) { 140 | rCtx.stream = stream; 141 | rCtx.video.srcObject = stream; 142 | rCtx.video.play(); 143 | rCtx.recordMedia(stream, true); //Recording Video 144 | }) 145 | .catch(function(err) { 146 | console.log("An error occurred: " + err); 147 | }); 148 | 149 | } 150 | 151 | 152 | // The width and height of the captured photo. We will set the 153 | // width to the value defined here, but the height will be 154 | // calculated based on the aspect ratio of the input stream. 155 | 156 | var width = 640; // We will scale the photo width to this 157 | var height = 0; // This will be computed based on the input stream 158 | height = this.video.videoHeight / (this.video.videoWidth/width); 159 | 160 | // |streaming| indicates whether or not we're currently streaming 161 | // video from the camera. Obviously, we start at false. 162 | 163 | var streaming = false; 164 | 165 | this.video.addEventListener('canplay', function(ev){ 166 | if (!streaming) { 167 | 168 | // Firefox currently has a bug where the height can't be read from 169 | // the video, so we will make assumptions if this happens. 170 | 171 | if (isNaN(height)) { 172 | height = width / (4/3); 173 | } 174 | 175 | 176 | rCtx.video.setAttribute('width', width); 177 | rCtx.video.setAttribute('height', height); 178 | rCtx.canvas.setAttribute('width', width); 179 | rCtx.canvas.setAttribute('height', height); 180 | streaming = true; 181 | } 182 | }, false); 183 | 184 | var photo_button = document.getElementById('photo_button'); 185 | var record_buttons = document.getElementById('buttons'); 186 | if(this.videoRecorder) { 187 | photo_button.style.display = "none"; 188 | record_buttons.style.display = "block"; 189 | } else { 190 | photo_button.style.display = "inline-block"; 191 | record_buttons.style.display = "none"; 192 | } 193 | while (rCtx.camera.lastElementChild) { 194 | if(rCtx.camera.lastElementChild.classList.contains('clip')) 195 | rCtx.camera.removeChild(rCtx.camera.lastElementChild); 196 | else 197 | break; 198 | } 199 | 200 | photo_button.addEventListener('click', function(ev){ 201 | takepicture(); 202 | ev.preventDefault(); 203 | document.getElementById("buttons").style.display = "none"; 204 | }, false); 205 | 206 | clearphoto(); 207 | 208 | 209 | // Fill the photo with an indication that none has been 210 | // captured. 211 | 212 | function clearphoto() { 213 | var context = rCtx.canvas.getContext('2d'); 214 | context.fillStyle = "#AAA"; 215 | context.fillRect(0, 0, rCtx.canvas.width, rCtx.canvas.height); 216 | 217 | var data = rCtx.canvas.toDataURL('image/png'); 218 | 219 | rCtx.photo.setAttribute('src', data); 220 | } 221 | 222 | // Capture a photo by fetching the current contents of the video 223 | // and drawing it into a canvas, then converting that to a PNG 224 | // format data URL. By drawing it on an offscreen canvas and then 225 | // drawing that to the screen, we can change its size and/or apply 226 | // other changes before drawing it. 227 | 228 | function takepicture() { 229 | var context = rCtx.canvas.getContext('2d'); 230 | if (width && height) { 231 | rCtx.canvas.width = width; 232 | rCtx.canvas.height = height; 233 | context.drawImage(rCtx.video, 0, 0, width, height); 234 | 235 | var data = rCtx.canvas.toDataURL('image/png'); 236 | 237 | rCtx.photo.setAttribute('src', data); 238 | rCtx.photo.style.display = "inline-block"; 239 | 240 | rCtx.video.style.display = "none"; 241 | 242 | const buttonContainer = document.createElement('article'); 243 | const cancelButton = document.createElement('button'); 244 | const sendButton = document.createElement('button'); 245 | 246 | buttonContainer.classList.add('clip'); 247 | buttonContainer.style.textAlign = 'center'; 248 | cancelButton.textContent = 'Cancel'; 249 | cancelButton.className = 'cancel btn btn-danger'; 250 | cancelButton.style.marginTop = '5px'; 251 | cancelButton.style.marginLeft = '5px'; 252 | 253 | sendButton.textContent = 'Send'; 254 | sendButton.className = 'send btn btn-success'; 255 | sendButton.style.marginTop = '5px'; 256 | 257 | buttonContainer.appendChild(sendButton); 258 | buttonContainer.appendChild(cancelButton); 259 | 260 | while (rCtx.camera.lastElementChild) { 261 | if(rCtx.camera.lastElementChild.classList.contains('clip')) 262 | rCtx.camera.removeChild(rCtx.camera.lastElementChild); 263 | else 264 | break; 265 | } 266 | 267 | rCtx.camera.appendChild(buttonContainer); 268 | document.getElementById('photo_button').style.display = "none"; 269 | 270 | 271 | function resetRecorder(e){ 272 | let evtTgt = e.target; 273 | evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode); 274 | document.getElementById("buttons").style.display = "block"; 275 | document.getElementById("recording_area").style.display = "block"; 276 | } 277 | 278 | cancelButton.onclick = function(e) { 279 | resetRecorder(e); 280 | rCtx.pictureRecorder(); 281 | rCtx.initPictureRecording(); 282 | } 283 | 284 | sendButton.onclick = function(e) { 285 | var blob = rCtx._dataURItoBlob(data); 286 | var image = rCtx._blobToFile(blob, "capture-"+ Date.now()+".png", {type: "image/png"}) 287 | rCtx.sendRecordedFile(image); 288 | MesiboLog("close stream", rCtx.stream, rCtx.stream.getTracks()); 289 | rCtx.scope.closeRecorder(); //TBD: Should we close the recorder when the send button is clicked 290 | } 291 | 292 | 293 | } else { 294 | clearphoto(); 295 | } 296 | } 297 | 298 | } 299 | 300 | MesiboRecorder.prototype.recordMedia = function(stream, video){ 301 | MesiboLog("recordMedia", stream, video); 302 | if(!stream) 303 | return; 304 | 305 | let chunks = []; 306 | let rCtx = this; 307 | 308 | const mediaRecorder = new MediaRecorder(stream); 309 | 310 | rCtx.stream = stream; 311 | if(!video){ 312 | MesiboLog("Activate audio visuals"); 313 | rCtx.visualize(stream); 314 | } 315 | 316 | rCtx.record.onclick = function() { 317 | mediaRecorder.start(); 318 | console.log(mediaRecorder.state); 319 | console.log("recorder started"); 320 | rCtx.record.style.background = "red"; 321 | 322 | rCtx.stop.disabled = false; 323 | rCtx.record.disabled = true; 324 | 325 | let pb = document.getElementById("photo_button"); 326 | if(pb) 327 | pb.disabled = true; 328 | } 329 | 330 | rCtx.stop.onclick = function() { 331 | mediaRecorder.stop(); 332 | console.log(mediaRecorder.state); 333 | console.log("recorder stopped"); 334 | rCtx.record.style.background = ""; 335 | rCtx.record.style.color = ""; 336 | 337 | rCtx.stop.disabled = true; 338 | rCtx.record.disabled = false; 339 | } 340 | 341 | mediaRecorder.onstop = function(e) { 342 | console.log("data available after MediaRecorder.stop() called."); 343 | 344 | // const clipName = prompt('Enter a name for your sound clip?','My unnamed clip'); 345 | const clipName = null; //Disabling clip name/caption for now 346 | 347 | const clipContainer = document.createElement('article'); 348 | const clipLabel = document.createElement('p'); 349 | 350 | var media = null; 351 | if(video){ 352 | document.getElementById("recording_area").style.display = "none"; 353 | media = document.createElement('video'); 354 | media.width = "320"; 355 | media.height = "240"; 356 | } 357 | else{ 358 | media = document.createElement('audio'); 359 | } 360 | const cancelButton = document.createElement('button'); 361 | const sendButton = document.createElement('button'); 362 | 363 | clipContainer.classList.add('clip'); 364 | clipContainer.style.textAlign = 'center'; 365 | cancelButton.textContent = 'Cancel'; 366 | cancelButton.className = 'cancel btn btn-danger'; 367 | cancelButton.style.marginLeft = '5px'; 368 | sendButton.textContent = 'Send'; 369 | sendButton.className = 'send btn btn-success ml-1'; 370 | 371 | if(clipName === null) { 372 | clipLabel.textContent = ''; 373 | } else { 374 | clipLabel.textContent = clipName; 375 | } 376 | 377 | rCtx.canvas.style.display = "none"; 378 | rCtx.record.style.display = "none"; 379 | rCtx.stop.style.display = "none"; 380 | 381 | clipContainer.appendChild(media); 382 | clipContainer.appendChild(clipLabel); 383 | clipContainer.appendChild(sendButton); 384 | clipContainer.appendChild(cancelButton); 385 | 386 | while (rCtx.mediaClips.lastElementChild) { 387 | rCtx.mediaClips.removeChild(rCtx.mediaClips.lastElementChild); 388 | } 389 | rCtx.mediaClips.appendChild(clipContainer); 390 | rCtx.mediaClips.style.display = "block"; 391 | 392 | media.controls = true; 393 | 394 | var blob = null; 395 | if(video) 396 | blob = new Blob(chunks, { 'type' : 'video/mp4; codecs=opus' }); 397 | else 398 | blob = new Blob(chunks, { 'type' : 'audio/webm; codecs=opus' }); 399 | chunks = []; 400 | const mediaUrl = window.URL.createObjectURL(blob); 401 | media.src = mediaUrl; 402 | 403 | var mediaFile = null; 404 | 405 | if(video) 406 | mediaFile = rCtx._blobToFile(blob, clipLabel.textContent + '.mp4', {type: "video/mp4"}); 407 | else 408 | mediaFile = rCtx._blobToFile(blob, clipLabel.textContent + '.wav', {type: "audio/wav"}); 409 | 410 | function resetRecorder(e){ 411 | 412 | let evtTgt = e.target; 413 | evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode); 414 | 415 | if(!video) 416 | rCtx.canvas.style.display = "initial"; 417 | rCtx.record.style.display = "inline-block"; 418 | rCtx.stop.style.display = "inline-block"; 419 | 420 | rCtx.record.style.background = ""; 421 | rCtx.record.style.color = ""; 422 | 423 | if(video){ 424 | this.photo_button.disabled = false; 425 | document.getElementById("buttons").style.display = "block"; 426 | document.getElementById("recording_area").style.display = "block"; 427 | } 428 | 429 | } 430 | 431 | cancelButton.onclick = function(e) { 432 | resetRecorder(e); 433 | } 434 | 435 | sendButton.onclick = function(e) { 436 | rCtx.sendRecordedFile(mediaFile); 437 | MesiboLog("close stream", rCtx.stream); 438 | rCtx.scope.closeRecorder(); //TBD: Should we close the recorder when the send button is clicked 439 | } 440 | 441 | } 442 | 443 | mediaRecorder.ondataavailable = function(e) { 444 | chunks.push(e.data); 445 | } 446 | } 447 | 448 | 449 | 450 | //main block for doing the audio recording 451 | MesiboRecorder.prototype.initAudioRecording = function(){ 452 | MesiboLog("initAudioRecording called.."); 453 | this.canvas.height = "60"; 454 | 455 | if (!navigator.mediaDevices.getUserMedia){ 456 | console.log('getUserMedia not supported on your browser!'); 457 | return -1; 458 | } 459 | 460 | const constraints = { audio: true }; 461 | let chunks = []; 462 | 463 | let rCtx = window.rCtx; 464 | let onSuccess = function(stream) { 465 | rCtx.recordMedia(stream, false); //Audio Recording 466 | } 467 | 468 | let onError = function(err) { 469 | console.log('The following error occured: ' + err); 470 | } 471 | 472 | navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); 473 | 474 | } 475 | 476 | MesiboRecorder.prototype._blobToFile = function(b, fileName, options){ 477 | var file = new File([b], fileName, options); 478 | MesiboLog("_blobToFile, generated file of type", file.type, file.name) 479 | return file; 480 | } 481 | 482 | MesiboRecorder.prototype._dataURItoBlob = function(dataURI) { 483 | // convert base64 to raw binary data held in a string 484 | // doesn't handle URLEncoded DataURIs 485 | var byteString = atob(dataURI.split(',')[1]); 486 | 487 | // separate out the mime component 488 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] 489 | 490 | // write the bytes of the string to an ArrayBuffer 491 | var ab = new ArrayBuffer(byteString.length); 492 | 493 | // create a view into the buffer 494 | var ia = new Uint8Array(ab); 495 | 496 | // set the bytes of the buffer to the correct values 497 | for (var i = 0; i < byteString.length; i++) { 498 | ia[i] = byteString.charCodeAt(i); 499 | } 500 | 501 | // write the ArrayBuffer to a blob, and you're done 502 | var blob = new Blob([ab], { 503 | type: mimeString 504 | }); 505 | return blob; 506 | 507 | } 508 | 509 | MesiboRecorder.prototype.sendRecordedFile = function(f){ 510 | MesiboLog("sendRecordedFile", f); 511 | this.scope.selected_file = f; 512 | this.scope.sendFile(); 513 | } 514 | 515 | MesiboRecorder.prototype.visualize = function(stream) { 516 | MesiboLog("visualize", stream); 517 | if(!this.audioCtx) { 518 | this.audioCtx = new AudioContext(); 519 | } 520 | 521 | const source = this.audioCtx.createMediaStreamSource(stream); 522 | 523 | const analyser = this.audioCtx.createAnalyser(); 524 | analyser.fftSize = 2048; 525 | const bufferLength = analyser.frequencyBinCount; 526 | const dataArray = new Uint8Array(bufferLength); 527 | 528 | source.connect(analyser); 529 | //analyser.connect(audioCtx.destination); 530 | 531 | draw(); 532 | 533 | function draw() { 534 | let rCtx = window.rCtx; 535 | const WIDTH = rCtx.canvas.width; 536 | const HEIGHT = rCtx.canvas.height; 537 | 538 | requestAnimationFrame(draw); 539 | 540 | analyser.getByteTimeDomainData(dataArray); 541 | 542 | rCtx.canvasCtx.fillStyle = 'rgb(200, 200, 200)'; 543 | rCtx.canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); 544 | 545 | rCtx.canvasCtx.lineWidth = 2; 546 | rCtx.canvasCtx.strokeStyle = 'rgb(0, 0, 0)'; 547 | 548 | rCtx.canvasCtx.beginPath(); 549 | 550 | let sliceWidth = WIDTH * 1.0 / bufferLength; 551 | let x = 0; 552 | 553 | 554 | for(let i = 0; i < bufferLength; i++) { 555 | 556 | let v = dataArray[i] / 128.0; 557 | let y = v * HEIGHT/2; 558 | 559 | if(i === 0) { 560 | rCtx.canvasCtx.moveTo(x, y); 561 | } else { 562 | rCtx.canvasCtx.lineTo(x, y); 563 | } 564 | 565 | x += sliceWidth; 566 | } 567 | 568 | rCtx.canvasCtx.lineTo(rCtx.canvas.width, rCtx.canvas.height/2); 569 | rCtx.canvasCtx.stroke(); 570 | 571 | } 572 | 573 | } 574 | 575 | MesiboRecorder.prototype.close = function(){ 576 | MesiboLog("MesiboRecorder.close called", this.stream.getTracks(), this.type); 577 | 578 | this.stream.getTracks().forEach(function(track) { 579 | MesiboLog('close Track', track); 580 | track.stop(); 581 | }); 582 | 583 | } 584 | 585 | 586 | -------------------------------------------------------------------------------- /mesibo/utils.js: -------------------------------------------------------------------------------- 1 | //utils.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/samples/js-beta 41 | * 42 | * 43 | */ 44 | 45 | let decodeString = (s) => { 46 | // console.log(s); 47 | if(!s) 48 | return ""; 49 | return new TextDecoder("utf-8").decode(s); 50 | }; 51 | 52 | let isValidString = (ele)=>{ 53 | return isValid(ele) && ""!=ele; 54 | }; 55 | 56 | // One validation function for all file types 57 | let isValidFileType = (fName, fType)=> { 58 | var extensionLists = {}; //Create an object for all extension lists 59 | extensionLists.video = ['m4v', 'avi', 'mpg', 'mp4', 'webm', 'wmv', 'mov', 'qt', 'mkv', 'flv', 'mpeg', 'm2v', '3gp']; 60 | extensionLists.image = ['jpg', 'jpeg', 'gif', 'bmp', 'png', 'webp', 'svg', 'ico', 'tif', 'tiff']; 61 | extensionLists.audio = ['mp3', 'mp4', 'aac', 'flac', 'm4a', 'wav','wva', 'ogg', 'pcm']; 62 | extensionLists.document = ['doc', 'txt', 'pdf', 'docx', 'xls', 'xlx']; 63 | return extensionLists[fType].indexOf(fName.split('.').pop()) > -1; 64 | } 65 | 66 | let isValid = (ele)=>{ 67 | return null!=ele && undefined!=ele; 68 | }; 69 | 70 | let isValidImage = (fName) =>{ 71 | if(!fName) 72 | return false; 73 | 74 | return isValidFileType(fName, 'image'); 75 | }; 76 | 77 | let isValidVideo = (fName) =>{ 78 | if(!fName) 79 | return false; 80 | 81 | return isValidFileType(fName, 'video'); 82 | }; 83 | 84 | let isGroup = (user) => { 85 | if(!isValid(user)) 86 | return false; 87 | 88 | if(undefined == user.getGroupId()) 89 | return false; 90 | 91 | return (user.getGroupId() > 0); 92 | } 93 | 94 | const getUrlInText = (text) => { 95 | //This function returns the first url it finds in text, empty string if no url is found 96 | if(!isValidString(text)) 97 | return ""; 98 | var matches = text.match(/\bhttps?:\/\/\S+/gi); 99 | if(!isValid(matches) || matches.length == 0) 100 | return ""; 101 | 102 | return matches[0]; 103 | } 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /messenger.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mesibo Messenger 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 | 57 | 58 | 59 | 60 | You have no messages 61 |
62 | 63 |
64 |
65 | 66 | 67 |
{{getFirstLetter(u)}}
68 |
69 | 70 |
71 |
{{getNameFromMessage(u)}}
72 |
73 | 74 | 75 | {{getUserLastMessage(u)}} 76 |
77 |
78 |
79 |
{{getUserLastMessageTime(u)}}
80 |
{{getUserUnreadCount(u, $index)}}
81 |
82 |
83 |
84 | 85 | 86 |
87 |
88 | 89 |
Select a Contact
90 |
91 | 92 |
93 |
94 |
95 |
96 | You have no existing contacts 97 |
98 | 99 |
100 |
{{getUserName(u)}}
101 |
{{getUserStatus(u)}}
102 |
103 |
104 |
105 |
106 |
107 | 108 | 109 |
110 |
111 | 112 |
Profile
113 |
114 |
115 | 116 | 117 | 118 | Profile Photo 119 |
120 |
121 | 122 | 123 |
124 |

{{getUserName(display_profile)}}

125 |
+{{display_profile.getAddress()}}
126 |
{{getLastSeen(display_profile)}}
127 |
{{display_profile.getStatus()}}
128 | 129 |
130 |
131 |
132 | 133 |
134 |
135 | 136 | {{membersList.length}} Members 137 |
138 | 139 |
140 |
141 | 142 |
143 |
{{getUserName(u.getProfile())}}
144 |
{{getMemberType(u)}}
145 |
146 |
147 |
148 |
149 | 150 |
151 |
152 | 153 | 154 |
155 |
156 | 157 |
Forward Message to..
158 |
159 |
160 |
161 | You have no existing contacts 162 |
163 | 164 |
165 |
{{getUserName(u)}}
166 |
{{getUserStatus(u)}}
167 |
168 |
169 |
170 |
171 |
172 | 173 |
174 | 175 |
176 |
177 | 178 | 204 | 205 |
206 | 207 | 211 |
212 |
213 | {{ getMessageHeader(m) }} 214 |
215 |
216 | {{getSenderNameFromMessage(m)}} 217 |
218 |
219 | 220 | 221 | 230 |
231 | 232 | 233 |
234 |
235 | 236 |
237 |
238 |
{{m.title}}
239 |
Description 240 |
241 |

{{hostnameFromUrl(m.launchurl)}}

242 |
243 |
244 |
245 | 246 | 247 | 248 | 281 | 282 |
283 |
{{getMessageText(m)}}
284 |
285 | {{m.getTimestamp().getTime()}} 286 | 287 |
288 |
289 |
290 |
{{getMessageText(m)}}
291 |
292 |
293 | 294 |   295 |
296 | 297 | 298 | 299 | 300 |
301 |
302 | 303 |
304 |
305 |
{{link_preview.title}}
306 |
{{link_preview.description}}
307 |

{{link_preview.hostname}} 308 |

309 |
310 |
311 | 312 | 313 |
314 |
315 |
316 | 317 | 318 |
319 | 320 | 321 | 322 | 323 | 324 | 325 |
326 |
327 |
328 | 329 | 330 | 375 | 376 | 377 | 378 | 379 | 380 | 425 | 426 | 427 | 428 | 429 | 450 | 451 | 452 | 453 |
454 | 466 |
467 | 468 | 469 |
470 | 487 |
488 | 489 |
490 | 513 |
514 | 515 |
516 | 531 |
532 | 533 |
534 | 558 |
559 | 560 | 561 |
562 | 597 |
598 | 599 |
600 |
601 |
602 | 620 | 621 | 622 | -------------------------------------------------------------------------------- /scripts/controller.js: -------------------------------------------------------------------------------- 1 | //controller.js 2 | 3 | /** Copyright (c) 2023 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | 46 | 47 | //The number of messages loaded into the message area in one read call 48 | const MAX_MESSAGES_READ = 100; 49 | 50 | //The number of users to be loaded (summary) 51 | const MAX_MESSAGES_READ_SUMMARY = 100; 52 | 53 | const MAX_FILE_SIZE_SUPPORTED = 10000000; 54 | 55 | 56 | 57 | 58 | var mesiboWeb = angular.module('MesiboWeb', ['ngSanitize']); 59 | mesiboWeb.filter('unsafe', ['$sce', function ($sce) { 60 | return function (input) { 61 | return $sce.trustAsHtml(input); 62 | } 63 | }]); 64 | 65 | mesiboWeb.directive('imageonload', function() { 66 | return { 67 | restrict: 'A', 68 | link: function(scope, element, attrs) { 69 | if(!scope.$last) 70 | return; 71 | 72 | element.bind('load', function() { 73 | MesiboLog("image load") 74 | scrollToEnd(true); 75 | }); 76 | 77 | element.bind('error', function(){ 78 | ErrorLog('Error loading image'); 79 | }); 80 | } 81 | }; 82 | }); 83 | 84 | mesiboWeb.directive('videoonload', function() { 85 | return { 86 | restrict: 'A', 87 | link: function(scope, element, attrs) { 88 | if(!scope.$last) 89 | return; 90 | 91 | element.bind('loadeddata', function() { 92 | MesiboLog("video loadeddata"); 93 | scrollToEnd(true); 94 | }); 95 | 96 | element.bind('error', function(){ 97 | ErrorLog('Error loading video'); 98 | }); 99 | } 100 | }; 101 | }); 102 | 103 | 104 | mesiboWeb.directive('onFinishRender', function($timeout) { 105 | return { 106 | link: function(scope, element, attr) { 107 | if (scope.$last === true) { 108 | $timeout(function() { 109 | scope.$emit(attr.onFinishRender); 110 | }); 111 | } 112 | } 113 | }; 114 | }); 115 | 116 | mesiboWeb.directive("getTarget", function() { 117 | return { 118 | link: function(scope, element, attrs) { 119 | console.log(element); 120 | } 121 | } 122 | }); 123 | 124 | mesiboWeb.controller('AppController', ['$scope', '$window', '$anchorScroll', function ($scope, $window, $anchorScroll) { 125 | 126 | console.log("AppController loaded"); 127 | var token = getLoginToken(); 128 | if(!token || token.length < 16) { 129 | window.location.replace("index.html"); 130 | return; 131 | } 132 | 133 | $scope.isConnected = false; 134 | $scope.isLoggedIn = false; 135 | $scope.connection_status = ''; 136 | $scope.summarySession = null; 137 | $scope.msg_read_limit_reached = false; 138 | $scope.users_synced = false; 139 | $scope.scroll_messages = null; 140 | $scope.new_contact_name = ''; 141 | $scope.new_contact_phone = ''; 142 | 143 | $scope.messages = []; 144 | $scope.summary = []; 145 | 146 | $scope.selected_user = null; 147 | $scope.selected_user_count = 0; 148 | 149 | $scope.forward_message = null; 150 | 151 | $scope.mesibo = null; 152 | 153 | //Main UI 154 | $scope.display_profile = null; 155 | $scope.display_image = null; 156 | $scope.membersList = []; 157 | $scope.users_panel_show = false; 158 | $scope.message_area_show = false; 159 | 160 | //Input Area 161 | $scope.input_message_text = ""; 162 | $scope.link_preview = null; 163 | $scope.self_profile_name = ""; 164 | 165 | //Calls 166 | $scope.is_answer_call = false; 167 | $scope.is_video_call = false; 168 | $scope.is_voice_call = true; 169 | $scope.call_status = "Call Status: "; 170 | $scope.call_alert_message = ""; 171 | 172 | //Files 173 | $scope.selected_file = {}; 174 | $scope.input_file_caption = ""; 175 | 176 | //Recorder 177 | $scope.recorder = null; 178 | 179 | $scope.MAX_MEDIA_WIDTH = '320px'; 180 | $scope.MAX_MEDIA_HEIGHT = '240px'; 181 | 182 | $scope.MIN_MEDIA_WIDTH = '160px'; 183 | $scope.MIN_MEDIA_HEIGHT = '120px'; 184 | 185 | $scope.refresh = function(){ 186 | $scope.$applyAsync(); 187 | } 188 | 189 | $scope.scrollToLastMsg = function() { 190 | $scope.$$postDigest(function () { 191 | //$anchorScroll("messages_end"); 192 | scrollToEnd(false); 193 | }); 194 | } 195 | 196 | $scope.updateMessagesScroll = function(){ 197 | 198 | } 199 | 200 | $scope.$on('onMessagesRendered', function(e) { 201 | MesiboLog("onMessagesRendered"); 202 | if($scope.scroll_messages && 203 | $scope.scroll_messages.scrollTop == 0 204 | && $scope.messageSession 205 | && $scope.messages.length){ 206 | MesiboLog('onMessagesRendered'); 207 | } 208 | 209 | $scope.scrollToLastMsg(); 210 | 211 | }); 212 | 213 | 214 | angular.element(document.getElementById('messages')).bind('scroll', function(e){ 215 | //MesiboLog("scrolling"); 216 | $scope.checkScroll(e); 217 | }) 218 | 219 | 220 | $scope.checkScroll = function(e) { 221 | if(!(e && e.target)) 222 | return; 223 | 224 | $scope.scroll_messages = e.target; 225 | 226 | if($scope.scroll_messages.scrollTop == 0){ 227 | if(!$scope.messageSession){ 228 | return; 229 | } 230 | 231 | var m = $scope.messages.length; 232 | if(m == 0){ 233 | return; 234 | } 235 | 236 | MesiboLog("checkScroll: Scrolled to top!"); 237 | //Load more messages 238 | $scope.messageSession.read(MAX_MESSAGES_READ); 239 | } 240 | 241 | } 242 | 243 | 244 | $scope.getMesibo = function(){ 245 | return $scope.mesibo; 246 | } 247 | 248 | $scope.showAvailableUsers = function() { 249 | MesiboLog('showAvailableUsers'); 250 | $scope.users_panel_show = true; 251 | 252 | //prompt to add a contact if no contacts available 253 | if(!$scope.hasContacts()) 254 | $scope.showContactForm(); 255 | 256 | $scope.refresh(); 257 | } 258 | 259 | $scope.hideAvailableUsers = function() { 260 | MesiboLog('hideAvailableUsers'); 261 | $scope.users_panel_show = false; 262 | $scope.refresh(); 263 | } 264 | 265 | $scope.getContacts = function(){ 266 | var c = $scope.mesibo.getSortedProfiles(); 267 | return c; 268 | } 269 | 270 | $scope.hasContacts = function(){ 271 | var c = $scope.getContacts(); 272 | if(c && c.length) return true; 273 | return false; 274 | } 275 | 276 | // [OPTIONAL] refer to the comment below 277 | $scope.Mesibo_onGroupMembers = function(p, members) { 278 | $scope.membersList = members; 279 | $scope.refresh(); 280 | } 281 | 282 | $scope.showProfile = function(p) { 283 | if(!p) 284 | return; 285 | if(p.isSelfProfile()) { 286 | $scope.self_profile_name = p.getName(); 287 | } 288 | $scope.display_profile = p; 289 | $scope.display_image = $scope.display_profile.getImage(); 290 | 291 | // various profile information keys 292 | var values = p.getValues(); 293 | 294 | $scope.membersList = []; 295 | if(p.getGroupId() > 0) { 296 | // you can either pass a function or listenr, this code list 297 | // both teh syntax for reference 298 | if(true) { 299 | p.getMembers(0, false, $scope); 300 | } else { 301 | p.getMembers(0, false, function(p, members) { 302 | $scope.membersList = members; 303 | $scope.refresh(); 304 | }); 305 | } 306 | } 307 | $scope.refresh(); 308 | }; 309 | 310 | $scope.editProfile = function() { 311 | $scope.showProfile($scope.getSelfProfile()); 312 | } 313 | 314 | $scope.showProfileFromMessage = function(m) { 315 | var p = $scope.getProfileFromMessage(m); 316 | if(!p) 317 | return; 318 | $scope.showProfile(p); 319 | 320 | $scope.refresh(); 321 | }; 322 | 323 | $scope.hideProfileSettings = function() { 324 | $scope.display_profile = null; 325 | $scope.membersList = []; 326 | $scope.refresh(); 327 | }; 328 | 329 | $scope.hideForwardList = function() { 330 | $scope.forward_message = null; 331 | $scope.refresh(); 332 | }; 333 | 334 | //fm is the message to be forwarded 335 | $scope.showForwardList = function(fm){ 336 | if(!fm) 337 | return; 338 | 339 | $scope.forward_message = fm; 340 | $scope.refresh(); 341 | } 342 | 343 | $scope.showDeliveryTime = function(m) { 344 | if(!m || !m.isMessage() || !m.mid || !m.isOutgoing()) return false; 345 | if(!m.isDelivered() && !m.isReadByPeer()) return false; 346 | var o = document.getElementById('deltime-' + m.mid); 347 | if(!o) return false; 348 | m.getDeliveryTimestamp(null, function(d) { 349 | if(!d) o.style.display = 'none'; 350 | else o.innerHTML = "Delivered on: " + d.getDate(true) + " " + d.getTime(true); 351 | }); 352 | return true; 353 | } 354 | 355 | $scope.showReadTime = function(m) { 356 | if(!m || !m.isMessage() || !m.mid || !m.isOutgoing()) return false; 357 | if(!m.isReadByPeer()) return false; 358 | var o = document.getElementById('readtime-' + m.mid); 359 | if(!o) return false; 360 | m.getReadTimestamp(null, function(d) { 361 | if(!d) o.style.display = 'none'; 362 | else o.innerHTML = "Read on: " + d.getDate(true) + " " + d.getTime(true); 363 | }); 364 | return true; 365 | } 366 | 367 | $scope.showMessageHeader = function(m) { 368 | return (m.isForwarded() || m.isReply() || m.isModified()); 369 | } 370 | 371 | $scope.getMessageHeader = function(m) { 372 | if(m.isForwarded()) return "Forwarded"; 373 | if(m.isReply()) return "Reply"; 374 | if(m.isModified()) return "Edited"; 375 | return ""; 376 | } 377 | 378 | $scope.isSent = function(msg){ 379 | return isSentMessage(msg.status); 380 | } 381 | 382 | $scope.isReceived = function(msg){ 383 | return !isSentMessage(msg.status); 384 | } 385 | 386 | $scope.isMessageVisible = function(m) { 387 | return true; 388 | if(m.message || m.isDeleted() || $scope.isFileMsg(m)) return true; 389 | return false; 390 | } 391 | 392 | $scope.getMessageText = function(m) { 393 | if(m.isDeleted()) return "This message was deleted"; 394 | if(m.isDate()) 395 | return m.getTimestamp().getDate(true, "Today", "Yesterday"); 396 | 397 | if(!m.isCall()) 398 | return m.message; 399 | 400 | var type = m.isVideoCall()?"Video":"Audio"; 401 | var dir = "Missed"; 402 | if(m.isIncomingCall()) dir = "Incoming"; 403 | else if(m.isOutgoingCall()) dir = "Outgoing"; 404 | return dir + " " + type + " call at " + m.getTimestamp().getTime(); 405 | } 406 | 407 | $scope.getFormattedMessageText = function(m) { 408 | var text = $scope.getMessageText(m); 409 | if(!text) return ""; 410 | var t = text.split(/(https?:\/\/\S*)\b/); 411 | if(!t) return text; 412 | text = ''; 413 | for(var i=0; i < t.length; i++) { 414 | if(t[i].startsWith('https://') || t[i].startsWith('http://')) { 415 | text += '' + t[i] + ''; 416 | } else text += t[i]; 417 | } 418 | return text; 419 | 420 | } 421 | 422 | $scope.getLastSeen = function(p) { 423 | if(!p) return -1; 424 | return p.getLastSeen(); 425 | } 426 | 427 | $scope.isBlocked = function(p) { 428 | if(!p) return false; 429 | this.getLastSeen(p); 430 | return p.isBlocked(); 431 | } 432 | 433 | $scope.BlockUser = function(p) { 434 | var isBlocked = p.isBlocked(); 435 | p.setContact(true); 436 | p.subscribe(true); 437 | p.block(!isBlocked); 438 | p.save(); 439 | return true; 440 | } 441 | 442 | $scope.generateMessageArea = function(contact){ 443 | MesiboLog(contact); 444 | 445 | if($scope.selected_user && $scope.selected_user == contact){ 446 | return 0; 447 | } 448 | 449 | $scope.selected_user = contact; 450 | 451 | // Stop read session for previous user 452 | if($scope.messageSession) 453 | $scope.messageSession.stop(); 454 | 455 | $scope.messageSession = null; 456 | $scope.scroll_messages = null; 457 | $scope.sessionReadMessages($scope.selected_user, MAX_MESSAGES_READ); 458 | $scope.message_area_show = true; 459 | $scope.refresh(); 460 | $scope.scrollToLastMsg(); 461 | } 462 | 463 | $scope.onMemberClick = function(p) { 464 | $scope.hideProfileSettings(); 465 | if(p.isSelfProfile()) return; 466 | $scope.generateMessageArea(p); 467 | $scope.refresh(); 468 | } 469 | 470 | $scope.setSelectedUser = function(user){ 471 | $scope.selected_user = user; 472 | $scope.refresh(); 473 | } 474 | 475 | $scope.showContactForm = function(){ 476 | $('#ModalContactForm').modal("show"); 477 | } 478 | 479 | $scope.promptAddContact = function(){ 480 | $('#promptAddContact').modal("show"); 481 | } 482 | 483 | $scope.closePromptAddContact = function(){ 484 | $('#promptAddContact').modal("hide"); 485 | } 486 | 487 | $scope.hideContactForm = function(){ 488 | $('#ModalContactForm').modal("hide"); 489 | if(document.getElementById('contact-address')) 490 | document.getElementById('contact-address').value = ""; 491 | 492 | if(document.getElementById('contact-name')) 493 | document.getElementById('contact-name').value = ""; 494 | 495 | } 496 | 497 | $scope.addContact = function(){ 498 | //cAddress = document.getElementById('contact-address').value; 499 | //cGroupid = document.getElementById('contact-group-id').value; 500 | if($scope.new_contact_phone.length < 8){ 501 | alert("Enter valid phone number / address or a valid group id"); 502 | return; 503 | } 504 | 505 | if($scope.new_contact_phone[0] == "+") 506 | $scope.new_contact_phone = $scope.new_contact_phone.slice(1); 507 | 508 | var c = $scope.mesibo.getProfile($scope.new_contact_phone, 0); 509 | 510 | $scope.hideContactForm(); 511 | $scope.new_contact_name = ''; 512 | $scope.new_contact_phone = ''; 513 | 514 | //TBD: After adding new contact, select that 515 | $scope.generateMessageArea(c); 516 | 517 | $scope.refresh(); 518 | } 519 | 520 | 521 | $scope.isValidPreview = function(type){ 522 | MesiboLog("isValidPreview", type); 523 | 524 | if(type == "image"){ 525 | var e = document.getElementById("image-preview"); 526 | if(!e) 527 | return; 528 | 529 | MesiboLog(e); 530 | var fname = e.src; 531 | 532 | MesiboLog(fname); 533 | if(!fname) 534 | return; 535 | 536 | return isValidImage(fname); 537 | } 538 | 539 | if(type == "video"){ 540 | var e = document.getElementById("video-preview"); 541 | if(!e) 542 | return; 543 | 544 | var fname = e.src; 545 | if(!fname) 546 | return; 547 | 548 | return isValidVideo(fname); 549 | 550 | } 551 | 552 | return false; 553 | } 554 | 555 | $scope.getProfileFromMessage = function(m) { 556 | if(!m) return null; 557 | var p = m.groupProfile; 558 | if(!p) p = m.profile; 559 | return p; 560 | } 561 | 562 | $scope.getUserProfileFromMessage = function(m) { 563 | if(!m) return null; 564 | return m.profile; 565 | } 566 | 567 | $scope.hasPicture = function(m) { 568 | var p = $scope.getProfileFromMessage(m); 569 | if(!p) return false; 570 | var pic = p.getImage().getThumbnail(); 571 | if(pic) return true; 572 | return false; 573 | } 574 | 575 | $scope.getFirstLetter = function(m) { 576 | var p = $scope.getProfileFromMessage(m); 577 | if(!p) return '*'; 578 | var name = p.getNameOrAddress(''); 579 | if(!name) return '*'; 580 | for(var i=0; i < name.length; i++) { 581 | var c = name[i]; 582 | if(c >= 'a' && c <= 'z') return c; 583 | if(c >= 'A' && c <= 'Z') return c; 584 | if(c >= '0' && c <= '9') return c; 585 | } 586 | return name[0]; 587 | } 588 | 589 | $scope.hashCode = function(str) { 590 | if(!str || !str.length) return 0; 591 | var hash = 0; 592 | for (var i = 0; i < str.length; i++) { 593 | var chr = str.charCodeAt(i); 594 | hash = ((hash << 5) - hash) + chr; 595 | hash |= 0; 596 | } 597 | return hash; 598 | } 599 | 600 | $scope.getTextColorForProfile = function(p) { 601 | var colors = ["#e6d200", "#f58559", "#f9a43e", "#e4c62e", 602 | "#67bf74", "#59a2be", "#2093cd", "#ad62a7"]; 603 | if(!p) return colors[0]; 604 | var name = p.getNameOrAddress(); 605 | var l = name.length; 606 | if(!l) return colors[0]; 607 | var c = $scope.hashCode(name)&7; 608 | return colors[c]; 609 | } 610 | 611 | $scope.getLetterColor = function(m) { 612 | var p = $scope.getProfileFromMessage(m); 613 | return $scope.getTextColorForProfile(p); 614 | } 615 | 616 | $scope.getNameColor = function(m) { 617 | var p = $scope.getUserProfileFromMessage(m); 618 | return $scope.getTextColorForProfile(p); 619 | } 620 | 621 | $scope.getPictureFromMessage = function(m) { 622 | var p = $scope.getProfileFromMessage(m); 623 | return $scope.getUserPicture(p); 624 | } 625 | 626 | $scope.getNameFromMessage = function(m) { 627 | var p = $scope.getProfileFromMessage(m); 628 | return $scope.getUserName(p); 629 | } 630 | 631 | $scope.getSenderNameFromMessage = function(m) { 632 | if(null == m || null == m.profile) { 633 | return ''; // data object 634 | } 635 | return m.profile.getNameOrAddress(); 636 | } 637 | 638 | $scope.getUserPicture = function(user){ 639 | if(!user) return ''; 640 | // MesiboLog(user); 641 | var pic = user.getImage().getThumbnail(); 642 | if(pic) return pic; 643 | 644 | return user.getGroupId() ? MESIBO_DEFAULT_GROUP_IMAGE:MESIBO_DEFAULT_PROFILE_IMAGE; 645 | } 646 | 647 | $scope.getProfileImage = function(user){ 648 | if(!user) return ''; 649 | // MesiboLog(user); 650 | //var pic = user.getImage().getImageOrThumbnail(); 651 | var pic = $scope.display_image.getImageOrThumbnail(); 652 | if(pic) return pic; 653 | 654 | return user.getGroupId() ? MESIBO_DEFAULT_GROUP_IMAGE:MESIBO_DEFAULT_PROFILE_IMAGE; 655 | } 656 | 657 | $scope.changeProfilePicture = function() { 658 | var i = $scope.display_image.getNext(); 659 | if(i) 660 | $scope.display_image = i; 661 | else 662 | $scope.display_image = $scope.display_profile.getImage(); // restart 663 | } 664 | 665 | $scope.getUserName = function(user){ 666 | if(!user) return ""; 667 | return user.getNameOrAddress(); 668 | } 669 | 670 | $scope.getLastSeen = function(user){ 671 | if(!user) return ""; 672 | var lastseen = user.getLastSeen(); 673 | if(user.isOnline()) 674 | return "Online"; 675 | if(!lastseen) return "" 676 | 677 | return "Last seen: " + lastseen.getDateInNaturalLanguage(0); 678 | } 679 | 680 | $scope.getUserStatus = function(user){ 681 | // MesiboLog("getUserName", user); 682 | if(!user) return ""; 683 | return user.getString("status", ""); 684 | } 685 | 686 | $scope.getMemberType = function(m){ 687 | if(m.isOwner()) return "Group Owner"; 688 | if(m.isAdmin()) return "Group Admin"; 689 | return "Member"; 690 | } 691 | 692 | $scope.getMemberInfo = function(p){ 693 | return ""; 694 | } 695 | 696 | $scope.getUserLastMessage = function(m){ 697 | 698 | var profile = m.profile; 699 | 700 | if(!profile) { 701 | return ""; 702 | } 703 | 704 | if(profile.isGroup() && profile.isTypingInGroup(m['groupid'])) 705 | return "typing..."; 706 | 707 | if(m.filetype) 708 | return getFileTypeDescription(m); 709 | 710 | return this.getMessageText(m); 711 | } 712 | 713 | $scope.getUserLastMessageTime = function(m) { 714 | var t = m.getTimestamp(); 715 | if(t.getDaysElapsed()) return t.getDate(true, "Today", "Yesterday"); 716 | return t.getTime(); 717 | } 718 | 719 | $scope.getUserUnreadCount = function(m, index){ 720 | var p = $scope.getProfileFromMessage(m); 721 | 722 | var rs = p.createReadSession(null); 723 | 724 | rs.getUnreadCount( function on_unread(count){ 725 | //console.log("getUnreadCount from db", "a: "+ user.getAddress(), "g: "+ user.getGroupId(), "c: "+ count); 726 | if(!count) 727 | count = ""; 728 | 729 | document.getElementById("unread_count_"+ index).innerHTML = count; 730 | }); 731 | } 732 | 733 | $scope.getMessageStatusClass = function(m){ 734 | if(!isValid(m)) 735 | return ""; 736 | 737 | if($scope.isReceived(m) || MESIBO_MSGSTATUS_EMPTY == m.status){ 738 | return ""; 739 | } 740 | 741 | var status = m.status; 742 | var status_class = getStatusClass(status); 743 | if(!isValidString(status_class)) 744 | return -1; 745 | 746 | return status_class; 747 | } 748 | 749 | $scope.getFileName = function(m){ 750 | if(!m) 751 | return; 752 | 753 | var name = m.getFileName(); 754 | if(name) return name; 755 | 756 | if(m.title) 757 | return m.title; 758 | 759 | var fileUrl = m.fileurl; 760 | if(!fileUrl) 761 | return; 762 | 763 | var f = fileUrl.split("/"); 764 | if(!(f && f.length)) 765 | return; 766 | 767 | var fname = f[f.length - 1]; 768 | return fname; 769 | } 770 | 771 | $scope.getVideoWidth = function(e){ 772 | MesiboLog("getVideoWidth", e); 773 | } 774 | 775 | $scope.getVideoHeight = function(e){ 776 | MesiboLog("getVideoHeight", e); 777 | } 778 | 779 | $scope.setLinkPreview = function(lp){ 780 | $scope.link_preview = lp; 781 | $scope.refresh(); 782 | } 783 | 784 | $scope.closeLinkPreview = function(){ 785 | $scope.link_preview = null; 786 | $scope.refresh(); 787 | } 788 | 789 | $scope.inputTextChanged = async function(){ 790 | MesiboLog('inputTextChanged'); 791 | if(isLinkPreview){ 792 | } 793 | } 794 | 795 | $scope.getUserActivity = function(u) { 796 | if(!u) return ""; 797 | if(u.getGroupId() > 0) return ""; // This is not correct/complete as we can still show a user typing 798 | 799 | if(u.isTyping()) return "typing..."; 800 | if(u.isChatting()) return "chatting with you..."; 801 | if(u.isOnline()) return "online"; 802 | return ""; 803 | } 804 | 805 | $scope.getLastMessageColor = function(m) { 806 | var profile = m.profile; 807 | if(profile && profile.isTypingInGroup(m.groupid)) return "#008800"; 808 | return "#000000"; 809 | } 810 | 811 | $scope.getMessageStatusColor = function(m){ 812 | // MesiboLog("getMessageStatusColor", m); 813 | if(!isValid(m)) 814 | return ""; 815 | 816 | if($scope.isReceived(m)) 817 | return ""; 818 | 819 | if(m.isDate()) 820 | return "#777777"; 821 | 822 | if(m.isCall()) 823 | return "#CC0000"; 824 | 825 | var status = m.status; 826 | var status_color = getStatusColor(status); 827 | if(!isValidString(status_color)) 828 | return ""; 829 | 830 | return status_color; 831 | } 832 | 833 | $scope.getMessageColor = function(m){ 834 | if(m.isDate()) 835 | return "#777777"; 836 | 837 | if(m.isCall()) 838 | return "#CC0000"; 839 | 840 | return "#000000"; 841 | } 842 | 843 | $scope.isOnlineFromMessage = function(m){ 844 | return false; 845 | 846 | var profile = m.profile; 847 | if(profile) return profile.isOnline(); 848 | return false; 849 | } 850 | 851 | $scope.deleteTokenInStorage = function(){ 852 | localStorage.removeItem("MESIBO_MESSENGER_TOKEN"); 853 | } 854 | 855 | $scope.logout = function(authfail){ 856 | $scope.mesibo.stop(); 857 | if(hasHardCodedLoginToken()) { 858 | if(authfail) 859 | showAuthFailAlert(); 860 | return; 861 | } 862 | 863 | if(authfail) $scope.deleteTokenInStorage(); 864 | window.location.replace("index.html"); 865 | } 866 | 867 | $scope.getFileIcon = function(f){ 868 | return getFileIcon(f); 869 | } 870 | 871 | $scope.updateSummary = function() { 872 | if(!$scope.summarySession) return; 873 | 874 | var msgs = $scope.summarySession.getMessages(); 875 | if(msgs && msgs.length > 0){ 876 | var m = msgs[0]; 877 | $scope.generateMessageArea($scope.getProfileFromMessage(m)); 878 | } 879 | 880 | $scope.refresh() 881 | } 882 | 883 | $scope.summaryListener = {}; 884 | $scope.summaryListener.Mesibo_onMessage = function(m) { 885 | if(m) $scope.summary.push(m); 886 | 887 | if(m && !m.isLastMessage()) { 888 | return; 889 | } 890 | 891 | if(isMessageSync && !m && !$scope.users_synced) { 892 | MesiboLog("Run out of users to display. Syncing.."); 893 | $scope.summarySession.sync(this.readCount); 894 | return; 895 | } 896 | 897 | $scope.updateSummary(); 898 | } 899 | 900 | $scope.summaryListener.Mesibo_onSync = function(count) { 901 | $scope.users_synced = true; 902 | if(!count) return; 903 | $scope.updateSummary(); 904 | } 905 | 906 | $scope.sessionReadSummary = function(){ 907 | $scope.summary.length = 0; 908 | $scope.summarySession = MesiboReadSession.createReadSummarySession($scope.summaryListener); 909 | $scope.summarySession.readCount = MAX_MESSAGES_READ_SUMMARY; 910 | $scope.summarySession.read(MAX_MESSAGES_READ_SUMMARY); 911 | } 912 | 913 | $scope.getSummary = function() { 914 | if($scope.summarySession) { 915 | var m = $scope.summarySession.getMessages(); 916 | if(m) return m; 917 | } 918 | return []; 919 | } 920 | 921 | $scope.getMessages = function() { 922 | return $scope.messages; 923 | } 924 | 925 | $scope.showName = function(m) { 926 | if(!m.isMessage() || !m.isIncoming() || !m.isGroupMessage()) 927 | return false; 928 | 929 | if(!$scope.messages.length || !$scope.messages) return false; 930 | 931 | const cb = (element) => element === m; 932 | var i = $scope.messages.findIndex(cb); 933 | if(i <= 0) return true; 934 | var prev = $scope.messages[i-1]; 935 | 936 | if(prev.profile != m.profile || !prev.isIncoming() || !prev.isMessage()) 937 | return true; 938 | 939 | return false; 940 | } 941 | 942 | $scope.Mesibo_onMessage = async function(m) { 943 | //MesiboLog("$scope.prototype.OnMessage", m); 944 | if(isMessageSync && !m){ 945 | MesiboLog("Run out of messages to display. Syncing.."); 946 | $scope.msg_read_limit_reached = true; 947 | $scope.messageSession.sync(this.readCount); 948 | } 949 | 950 | if(!m) { 951 | $scope.refresh(); 952 | return; 953 | } 954 | 955 | if(!m.mid || m.presence) 956 | return; 957 | 958 | if(!$scope.selected_user) { 959 | $scope.updateSummary(); 960 | return; 961 | } 962 | 963 | if(!m.isDestinedFor($scope.selected_user)) { 964 | //$scope.refresh(); // to update summary 965 | return; 966 | } 967 | 968 | var prev = null; 969 | if(m.isRealtimeMessage()) { 970 | if($scope.messages.length) { 971 | prev = $scope.messages[$scope.messages.length-1]; 972 | } 973 | 974 | 975 | if(!prev || (!prev.isDate() && prev.getTimestamp().getDaysElapsed() != m.getTimestamp().getDaysElapsed())) { 976 | var d = m.cloneDate(); 977 | $scope.messages.push(d); 978 | } 979 | 980 | $scope.messages.push(m); 981 | } 982 | else { 983 | if($scope.messages.length) { 984 | prev = $scope.messages[0]; 985 | } 986 | 987 | if(prev && !prev.isDate() && prev.getTimestamp().getDaysElapsed() != m.getTimestamp().getDaysElapsed()) { 988 | var d = prev.cloneDate(); 989 | $scope.messages.unshift(d); 990 | } 991 | 992 | $scope.messages.unshift(m); 993 | 994 | if(m.isLastMessage()) { 995 | var d = m.cloneDate(); 996 | $scope.messages.unshift(d); 997 | } 998 | } 999 | 1000 | if(m.isRealtimeMessage() || m.isLastMessage()){ 1001 | $scope.refresh(); 1002 | $scope.scrollToLastMsg(); 1003 | } 1004 | 1005 | return 0; 1006 | } 1007 | 1008 | $scope.Mesibo_onMessageUpdate = async function(m) { 1009 | $scope.refresh(); 1010 | return; 1011 | } 1012 | 1013 | $scope.Mesibo_onSync = function(rs, count) { 1014 | if(!count) return; 1015 | $scope.messageSession.read(MAX_MESSAGES_READ); 1016 | } 1017 | 1018 | $scope.Mesibo_onPresence = async function(m) { 1019 | // calling refresh is not optimized but keep it for now 1020 | $scope.refresh(); 1021 | } 1022 | 1023 | $scope.sessionReadMessages = function(user, count){ 1024 | MesiboLog("sessionReadMessages", user); 1025 | $scope.messages.length = 0; 1026 | $scope.messageSession = user.createReadSession($scope); 1027 | $scope.messageSession.enableReadReceipt(true); 1028 | $scope.messageSession.readCount = count; 1029 | $scope.messageSession.read(count); 1030 | } 1031 | 1032 | $scope.readMessages = function(userScrolled){ 1033 | 1034 | if($scope.messageSession) 1035 | $scope.messageSession.read(MAX_MESSAGES_READ); 1036 | else 1037 | $scope.sessionReadMessages($scope.selected_user, MAX_MESSAGES_READ); 1038 | } 1039 | 1040 | $scope.deleteSelectedMessage = async function(m, remote){ 1041 | if(!m) return; 1042 | 1043 | for(var i=0 ; !remote && i < $scope.messages.length; i++) { 1044 | if($scope.messages[i].mid == m.mid) { 1045 | $scope.messages.splice(i, 1); 1046 | break; 1047 | } 1048 | } 1049 | 1050 | if(remote) 1051 | await m.wipeAndRecall(); 1052 | else 1053 | await m.delete(); 1054 | 1055 | if(!$scope.messages.length) { 1056 | $scope.updateSummary(); 1057 | return; 1058 | } 1059 | $scope.refresh(); 1060 | return; 1061 | } 1062 | 1063 | $scope.deleteMessages = async function() { 1064 | if(!$scope.selected_user) 1065 | return; 1066 | 1067 | await $scope.selected_user.deleteMessages(); 1068 | $scope.selected_user = null; 1069 | 1070 | var s = $scope.getSummary(); 1071 | if(s.length) { 1072 | $scope.generateMessageArea($scope.getProfileFromMessage(s[0])); 1073 | } 1074 | $scope.refresh(); 1075 | } 1076 | 1077 | $scope.forwardMessageTo = function(to){ 1078 | MesiboLog("forwardMessageTo", to); 1079 | if(!to) 1080 | return; 1081 | 1082 | var m = $scope.forward_message; 1083 | MesiboLog(m, $scope.forward_message); 1084 | if(!m) 1085 | return; 1086 | 1087 | $scope.forwardSelectedMessage(m, to); 1088 | 1089 | $scope.forward_message = null; 1090 | $scope.refresh(); 1091 | 1092 | $scope.generateMessageArea(to); 1093 | } 1094 | 1095 | $scope.forwardSelectedMessage = function(m, to){ 1096 | MesiboLog("forwardSelectedMessage", m, to); 1097 | if(!m || !to) 1098 | return; 1099 | 1100 | m = m.forward(to); 1101 | m.send(); 1102 | 1103 | $scope.refresh(); 1104 | $scope.scrollToLastMsg(); 1105 | } 1106 | 1107 | $scope.resendSelectedMessage = function(m){ 1108 | MesiboLog("resendSelectedMessage", m); 1109 | if(!$scope.selected_user) 1110 | return; 1111 | 1112 | m.resend(); 1113 | } 1114 | 1115 | $scope.onKeydown = function(event){ 1116 | if(event.keyCode === 13) 1117 | $scope.sendMessage(); 1118 | else 1119 | $scope.selected_user.sendTyping(); 1120 | 1121 | //event.preventDefault(); 1122 | } 1123 | 1124 | //Send text message to peer(selected user) by reading text from input area 1125 | $scope.sendMessage = function() { 1126 | MesiboLog('sendMessage'); 1127 | 1128 | var value = $scope.input_message_text; 1129 | if(!value) 1130 | return -1; 1131 | 1132 | var m = $scope.selected_user.newMessage(); 1133 | m.message = value; 1134 | m.send(); 1135 | 1136 | $scope.input_message_text = ""; 1137 | $scope.refresh(); 1138 | $scope.scrollToLastMsg(); 1139 | return 0; 1140 | } 1141 | 1142 | $scope.makeVideoCall = function(){ 1143 | $scope.is_video_call = true; 1144 | $scope.call.videoCall(); 1145 | $scope.refresh(); 1146 | } 1147 | 1148 | $scope.makeVoiceCall = function(){ 1149 | $scope.is_voice_call = true; 1150 | $scope.call.voiceCall(); 1151 | $scope.refresh(); 1152 | } 1153 | 1154 | 1155 | $scope.hideAnswerModal = function(){ 1156 | $('#answerModal').modal("hide"); 1157 | $scope.is_answer_call = false; 1158 | $scope.refresh(); 1159 | } 1160 | 1161 | $scope.hangupCall = function(){ 1162 | $scope.mesibo.hangup(0); 1163 | $scope.hideAnswerModal(); 1164 | } 1165 | 1166 | 1167 | $scope.answerCall = function(){ 1168 | $scope.is_answer_call = true; 1169 | $scope.call.answer(); 1170 | $scope.refresh(); 1171 | } 1172 | 1173 | $scope.showRinging = function(){ 1174 | //$('#answerModal').modal({backdrop: 'static', keyboard: false}); 1175 | //$('#answerModal').modal({ show: true }); 1176 | $('#answerModal').modal("show"); 1177 | $scope.refresh(); 1178 | } 1179 | 1180 | $scope.hangupVideoCall = function(){ 1181 | $('#videoModal').modal("hide"); 1182 | $('#answerModal').modal("hide"); 1183 | $scope.is_video_call = false; 1184 | $scope.call.hangup(); 1185 | $scope.refresh(); 1186 | } 1187 | 1188 | $scope.hangupAudioCall = function(){ 1189 | $('#voiceModal').modal("hide"); 1190 | $('#answerModal').modal("hide"); 1191 | $scope.is_voice_call = false; 1192 | $scope.call.hangup(); 1193 | $scope.refresh(); 1194 | } 1195 | 1196 | $scope.showVideoCall = function(){ 1197 | $('#videoModal').modal("show"); 1198 | $scope.is_video_call = true; 1199 | $scope.refresh(); 1200 | } 1201 | 1202 | $scope.showVoiceCall = function(){ 1203 | $('#voiceModal').modal("show"); 1204 | $scope.is_voice_call = true; 1205 | $scope.refresh(); 1206 | } 1207 | 1208 | $scope.clickUploadFile = function(){ 1209 | setTimeout(function () { 1210 | angular.element('#upload').trigger('click'); 1211 | }, 0); 1212 | } 1213 | 1214 | $scope.onFileSelect = function(element){ 1215 | $scope.$apply(function(scope) { 1216 | var file = element.files[0]; 1217 | if(!file){ 1218 | MesiboLog("Invalid file"); 1219 | return -1; 1220 | } 1221 | 1222 | if(file.size > MAX_FILE_SIZE_SUPPORTED){ 1223 | MesiboLog("Uploaded file larger than supported(10 MB)"); 1224 | alert("Please select a file smaller than 10Mb"); 1225 | return; 1226 | } 1227 | 1228 | MesiboLog("Selected File =====>", file); 1229 | 1230 | $scope.selected_file = file; 1231 | $scope.showFilePreview(file); 1232 | MesiboLog('Reset', element.value); 1233 | element.value = ''; 1234 | 1235 | }); 1236 | } 1237 | 1238 | $scope.showFilePreview = function(f) { 1239 | var reader = new FileReader(); 1240 | $('#image-preview').attr('src', ""); 1241 | $('#video-preview').attr('src', ""); 1242 | $('#video-preview').hide(); 1243 | 1244 | reader.onload = function(e) { 1245 | if(isValidFileType(f.name, 'image')){ 1246 | $('#image-preview').attr('src', e.target.result); 1247 | $('#image-preview').show(); 1248 | } 1249 | else if(isValidFileType(f.name, 'video')){ 1250 | $('#video-preview').attr('src', e.target.result); 1251 | $('#video-preview').show(); 1252 | } 1253 | } 1254 | 1255 | reader.readAsDataURL(f); 1256 | 1257 | var s = document.getElementById("fileModalLabel"); 1258 | if (s) { 1259 | s.innerText = "Selected File " + f.name; 1260 | } 1261 | 1262 | $('#fileModal').modal("show"); 1263 | } 1264 | 1265 | $scope.openAudioRecorder = function(){ 1266 | $('#recorderModal').modal("show"); 1267 | document.getElementById("recorderModalLabel").innerHTML = "Audio Recorder"; 1268 | $scope.recorder = new MesiboRecorder($scope, "audio"); 1269 | $scope.recorder.initAudioRecording(); 1270 | } 1271 | 1272 | $scope.openPictureRecorder = function(){ 1273 | $('#recorderModal').modal("show"); 1274 | document.getElementById("recorderModalLabel").innerHTML = "Send a Picture"; 1275 | $scope.recorder = new MesiboRecorder($scope, "picture"); 1276 | $scope.recorder.initPictureRecording(); 1277 | } 1278 | 1279 | $scope.openVideoRecorder = function(){ 1280 | $('#recorderModal').modal("show"); 1281 | document.getElementById("recorderModalLabel").innerHTML = "Video Recorder"; 1282 | $scope.recorder = new MesiboRecorder($scope, "video"); 1283 | $scope.recorder.initPictureRecording(); 1284 | } 1285 | 1286 | $scope.closeRecorder = function(){ 1287 | MesiboLog("Closing recorder.., shutting down streams.", $scope.recorder); 1288 | $('#recorderModal').modal("hide"); 1289 | if(!$scope.recorder) 1290 | return; 1291 | $scope.recorder.close(); 1292 | $scope.recorder = null; 1293 | } 1294 | 1295 | $scope.closeFilePreview = function() { 1296 | $('#fileModal').modal("hide"); 1297 | $('#image-preview').hide(); 1298 | $('#video-preview').hide(); 1299 | //Clear selected file button attr 1300 | } 1301 | 1302 | $scope.selectProfilePicture = function(){ 1303 | setTimeout(function () { 1304 | angular.element('#profile-pic-input').trigger('click'); 1305 | }, 0); 1306 | } 1307 | 1308 | $scope.onProfileImageSelect = function(element){ 1309 | $scope.$apply(function(scope) { 1310 | var file = element.files[0]; 1311 | if(!file){ 1312 | MesiboLog("Invalid file"); 1313 | return -1; 1314 | } 1315 | 1316 | if(file.size > MAX_FILE_SIZE_SUPPORTED){ 1317 | MesiboLog("Uploaded file larger than supported(10 MB)"); 1318 | alert("Please select a file smaller than 10Mb"); 1319 | return; 1320 | } 1321 | 1322 | var c = $scope.mesibo.getSelfProfile(); 1323 | c.reset(); 1324 | c.setName($scope.self_profile_name); 1325 | c.setImage(file); 1326 | c.save(); 1327 | 1328 | $scope.refresh(); 1329 | 1330 | MesiboLog('Reset', element.value); 1331 | element.value = ''; 1332 | 1333 | }); 1334 | } 1335 | 1336 | $scope.setSelfProfileName = function(u){ 1337 | var c = $scope.mesibo.getSelfProfile(); 1338 | //c.picture = u.photo; 1339 | c.setName($scope.self_profile_name); 1340 | c.save(); 1341 | 1342 | $scope.refresh(); 1343 | } 1344 | 1345 | $scope.getSelfProfile = function(){ 1346 | return $scope.mesibo.getSelfProfile(); 1347 | } 1348 | 1349 | 1350 | $scope.setProfilePicture = function(){ 1351 | } 1352 | 1353 | 1354 | $scope.sendFile = function(){ 1355 | var m = $scope.selected_user.newMessage(); 1356 | m.setContent($scope.selected_file); 1357 | m.message = $scope.input_file_caption; 1358 | m.send(); 1359 | 1360 | $scope.input_file_caption = ''; 1361 | } 1362 | 1363 | $scope.isFileMsg = function(m){ 1364 | return isValid(m.filetype); 1365 | //return isValid(m.filetype) || m.fileurl.length > 0; 1366 | } 1367 | 1368 | $scope.isFailedMessage = function(m){ 1369 | if(!m || !m.isFailed()) 1370 | return false; 1371 | 1372 | return true; 1373 | } 1374 | 1375 | //Message contains URL Preview 1376 | $scope.isUrlMsg = function(m){ 1377 | return ($scope.isFileMsg(m) && !isValidString(m.fileurl)); 1378 | } 1379 | 1380 | $scope.Mesibo_onConnectionStatus = function(status){ 1381 | $scope.isConnected = false; 1382 | 1383 | MesiboLog("MesiboNotify.prototype.Mesibo_onConnectionStatus: " + status); 1384 | if(MESIBO_STATUS_SIGNOUT == status || MESIBO_STATUS_AUTHFAIL == status ){ 1385 | $scope.logout(MESIBO_STATUS_AUTHFAIL == status); 1386 | } 1387 | 1388 | var s =""; 1389 | switch(status){ 1390 | case MESIBO_STATUS_ONLINE: 1391 | s = ""; 1392 | $scope.isConnected = true; 1393 | break; 1394 | case MESIBO_STATUS_CONNECTING: 1395 | s = "Connecting.."; 1396 | break; 1397 | default: 1398 | s = "Not Connected"; 1399 | } 1400 | 1401 | $scope.connection_status = s; 1402 | $scope.refresh(); 1403 | } 1404 | 1405 | $scope.MesiboProfile_onPublish = function(p, result) { 1406 | MesiboLog("MesiboProfile_onPublish: " + result); 1407 | } 1408 | 1409 | $scope.Mesibo_onProfileUpdated = function(p){ 1410 | $scope.refresh(); 1411 | } 1412 | 1413 | /* customize profile if requires */ 1414 | $scope.Mesibo_onCustomizeProfile = function(p){ 1415 | return true; 1416 | } 1417 | 1418 | /* This function is invoked in the absence of a profile name to get a temporary name. */ 1419 | $scope.Mesibo_onGetProfileName = function(p){ 1420 | if(p.isGroup()) return null; 1421 | return "+" + p.getAddress(); 1422 | } 1423 | 1424 | /* This function is invoked in the absence of a profile image to get a temporary image. */ 1425 | $scope.Mesibo_onGetProfileImage = function(p){ 1426 | return null; 1427 | } 1428 | 1429 | $scope.Mesibo_onMessageStatus = function(m){ 1430 | MesiboLog("$scope.Mesibo_onMessageStatus", m); 1431 | $scope.refresh(); 1432 | } 1433 | 1434 | 1435 | $scope.Mesibo_onCall = function(callid, from, video){ 1436 | if(video){ 1437 | $scope.is_video_call = true; 1438 | $scope.mesibo.setupVideoCall("localVideo", "remoteVideo", true); 1439 | } 1440 | else{ 1441 | $scope.is_voice_call = true; 1442 | $scope.mesibo.setupVoiceCall("audioPlayer"); 1443 | } 1444 | 1445 | $scope.call_alert_message = "Incoming "+(video ? "Video" : "Voice")+" call from: "+from; 1446 | $scope.is_answer_call = true; 1447 | 1448 | $scope.showRinging(); 1449 | } 1450 | 1451 | $scope.Mesibo_onCallStatus = function(callid, status){ 1452 | 1453 | var s = ""; 1454 | 1455 | switch (status) { 1456 | case MESIBO_CALLSTATUS_RINGING: 1457 | s = "Ringing"; 1458 | break; 1459 | 1460 | case MESIBO_CALLSTATUS_ANSWER: 1461 | s = "Answered"; 1462 | break; 1463 | 1464 | case MESIBO_CALLSTATUS_BUSY: 1465 | s = "Busy"; 1466 | break; 1467 | 1468 | case MESIBO_CALLSTATUS_NOANSWER: 1469 | s = "No Answer"; 1470 | break; 1471 | 1472 | case MESIBO_CALLSTATUS_INVALIDDEST: 1473 | s = "Invalid Destination"; 1474 | break; 1475 | 1476 | case MESIBO_CALLSTATUS_UNREACHABLE: 1477 | s = "Unreachable"; 1478 | break; 1479 | 1480 | case MESIBO_CALLSTATUS_OFFLINE: 1481 | s = "Offline"; 1482 | break; 1483 | 1484 | case MESIBO_CALLSTATUS_COMPLETE: 1485 | s = "Complete"; 1486 | break; 1487 | } 1488 | 1489 | if(s) 1490 | $scope.call_status = "Call Status: " + s; 1491 | $scope.refresh(); 1492 | 1493 | if (status & MESIBO_CALLSTATUS_COMPLETE) { 1494 | if ($scope.is_video_call) 1495 | $scope.hangupVideoCall(); 1496 | else 1497 | $scope.hangupAudioCall(); 1498 | } 1499 | } 1500 | 1501 | $scope.init_messenger = function(){ 1502 | MesiboLog("init_messenger called"); 1503 | $scope.sessionReadSummary(); 1504 | $scope.call = new MesiboCall($scope); 1505 | $scope.file = new MessengerFile($scope); 1506 | } 1507 | 1508 | $scope.init_popup = function(){ 1509 | MesiboLog("init_popup called"); 1510 | $scope.selected_user = $scope.mesibo.getProfile(POPUP_DESTINATION_USER, 0); 1511 | $scope.activity = ""; 1512 | 1513 | $scope.call = new MesiboCall($scope); 1514 | $scope.file = new MessengerFile($scope); 1515 | 1516 | $scope.MAX_MEDIA_WIDTH = '180px'; 1517 | $scope.MAX_MEDIA_HEIGHT = '80px'; 1518 | 1519 | $scope.MIN_MEDIA_WIDTH = '50px'; 1520 | $scope.MIN_MEDIA_HEIGHT = '50px'; 1521 | 1522 | MesiboLog("sessionReadMessages", $scope.selected_user, MAX_MESSAGES_READ); 1523 | $scope.sessionReadMessages($scope.selected_user, MAX_MESSAGES_READ); 1524 | } 1525 | 1526 | $scope.toggleConnection = function(){ 1527 | if($scope.isConnected){ 1528 | MesiboLog("Stop Mesibo.."); 1529 | $scope.mesibo.stop(); 1530 | } 1531 | else{ 1532 | MesiboLog("Start Mesibo.."); 1533 | $scope.mesibo.start(); 1534 | } 1535 | } 1536 | 1537 | $scope.getToken = function() { 1538 | if(null == $scope.mesibo) 1539 | $scope.mesibo = Mesibo.getInstance(); 1540 | getMesiboDemoAppToken($scope.mesibo); 1541 | } 1542 | 1543 | $scope.initMesibo = function(demo_app_name){ 1544 | 1545 | if(demo_app_name == "multitab-popup"){ 1546 | // Instead of directly accessing Mesibo APIs like so, 1547 | // $scope.mesibo = new Mesibo(); 1548 | // use a wrapper API that uses a shared worker 1549 | $scope.mesibo = new MesiboWorker($scope); 1550 | } 1551 | 1552 | $scope.mesiboNotify = $scope; 1553 | 1554 | //Initialize Mesibo 1555 | if(!MESIBO_APP_ID || !getLoginToken()){ 1556 | alert("Invalid token or app-id. Check config.js"); 1557 | return; 1558 | } 1559 | 1560 | $scope.isLoggedIn = true; 1561 | $scope.mesibo = Mesibo.getInstance(); 1562 | $scope.mesibo.setAppName(MESIBO_APP_ID); 1563 | 1564 | /* Enable your users to log in from multiple browser windows and tabs. 1565 | * https://mesibo.com/documentation/api/messaging/sync-across-browser-tabs-windows/ 1566 | * 1567 | * This is a beta feature 1568 | */ 1569 | //$scope.mesibo.enableCrossTabSessions(true); 1570 | 1571 | $scope.mesibo.setCredentials(getLoginToken()); 1572 | $scope.mesibo.setListener($scope.mesiboNotify); 1573 | $scope.mesibo.setDatabase("mesibodb", function(init){ 1574 | MesiboLog("setDatabase", init); 1575 | 1576 | if(!init){ 1577 | ErrorLog("setDatabase failed"); 1578 | return; 1579 | } 1580 | 1581 | $scope.init_messenger(); 1582 | 1583 | }); 1584 | 1585 | $scope.mesibo.start(); 1586 | 1587 | $scope.refresh(); 1588 | } 1589 | 1590 | onControllerReady(); 1591 | console.log("AppController loading done"); 1592 | }]); 1593 | 1594 | 1595 | -------------------------------------------------------------------------------- /scripts/ui.js: -------------------------------------------------------------------------------- 1 | //ui.js 2 | 3 | /** Copyright (c) 2021 Mesibo 4 | * https://mesibo.com 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the terms and condition mentioned 9 | * on https://mesibo.com as well as following conditions are met: 10 | * 11 | * Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions, the following disclaimer and links to documentation and 13 | * source code repository. 14 | * 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * Neither the name of Mesibo nor the names of its contributors may be used to 20 | * endorse or promote products derived from this software without specific prior 21 | * written permission. 22 | * 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * Documentation 37 | * https://mesibo.com/documentation/ 38 | * 39 | * Source Code Repository 40 | * https://github.com/mesibo/messenger-javascript 41 | * 42 | * 43 | */ 44 | 45 | // Get the matching status tick icon 46 | let getStatusClass = (status) => { 47 | // MesiboLog("getStatusClass", status); 48 | var statusTick = ""; 49 | switch (status) { 50 | 51 | case MESIBO_MSGSTATUS_SENT: 52 | statusTick = "las la-check"; 53 | break; 54 | 55 | case MESIBO_MSGSTATUS_DELIVERED: 56 | statusTick = "las la-check-double"; 57 | break; 58 | 59 | 60 | case MESIBO_MSGSTATUS_READ: 61 | //statusTick = "fas fa-check-circle"; 62 | statusTick = "las la-check-double"; 63 | break; 64 | 65 | case MESIBO_MSGSTATUS_CALLMISSED: 66 | case MESIBO_MSGSTATUS_CALLINCOMING: 67 | case MESIBO_MSGSTATUS_CALLOUTGOING: 68 | //statusTick = "fas fa-check-circle"; 69 | statusTick = "las la-phone"; 70 | break; 71 | 72 | 73 | default: 74 | statusTick = "far fa-clock"; 75 | } 76 | 77 | //MESIBO_MSGSTATUS_FAIL is 0x80 78 | if(status > 127) 79 | statusTick = "fas fa-exclamation-circle"; 80 | 81 | return statusTick; 82 | }; 83 | 84 | let getActivityColor = () => { 85 | return "#00b700"; 86 | } 87 | 88 | // If the status value is read type, color it blue. Default color of status icon is gray 89 | let getStatusColor = (status) => { 90 | var statusColor = ""; 91 | switch (status) { 92 | case MESIBO_MSGSTATUS_READ: 93 | statusColor = "#34b7f1"; 94 | break; 95 | 96 | default: 97 | statusColor = "grey"; 98 | } 99 | //MESIBO_MSGSTATUS_FAIL is 0x80 100 | if(status > 127) 101 | statusColor = "red"; 102 | 103 | return statusColor; 104 | }; 105 | 106 | let getFileIcon = (f) =>{ 107 | 108 | if(!isValid(f)) 109 | return; 110 | 111 | var type = f.filetype; 112 | if(undefined == type) 113 | return ""; 114 | 115 | 116 | var fileIcon = "fas fa-paperclip"; 117 | switch (type) { 118 | 119 | //Image 120 | case MESIBO_FILETYPE_IMAGE: 121 | fileIcon = "fas fa-image"; 122 | break; 123 | 124 | //Video 125 | case MESIBO_FILETYPE_VIDEO: 126 | fileIcon = "fas fa-video"; 127 | break; 128 | 129 | //Audio 130 | case MESIBO_FILETYPE_AUDIO: 131 | fileIcon = "fas fa-music"; 132 | break; 133 | 134 | //Location 135 | case MESIBO_FILETYPE_LOCATION: 136 | fileIcon = "fas fa-map-marker-alt"; 137 | } 138 | 139 | return fileIcon; 140 | 141 | } 142 | 143 | let getFileTypeDescription = (f) =>{ 144 | 145 | var type = f.filetype; 146 | if(!isValid(type)) 147 | return ""; 148 | 149 | var fileType = "Attachment"; 150 | switch (type) { 151 | 152 | //Image 153 | case MESIBO_FILETYPE_IMAGE: 154 | fileType = "Image"; 155 | break; 156 | 157 | //Video 158 | case MESIBO_FILETYPE_VIDEO: 159 | fileType = "Video"; 160 | break; 161 | 162 | //Audio 163 | case MESIBO_FILETYPE_AUDIO: 164 | fileType = "Audio"; 165 | break; 166 | 167 | //Location 168 | case MESIBO_FILETYPE_LOCATION: 169 | fileType = "Location"; 170 | } 171 | 172 | //xxTODOxx: For link preview 173 | // if(isValidString(f.launchurl)) 174 | // filetype = "Link" 175 | 176 | return fileType; 177 | 178 | } 179 | 180 | let isSentMessage = (status) =>{ 181 | if(status == MESIBO_MSGSTATUS_RECEIVEDREAD || status == MESIBO_MSGSTATUS_RECEIVEDNEW || MESIBO_MSGSTATUS_WIPED == status || MESIBO_MSGSTATUS_DELETED == status) 182 | return false; 183 | else 184 | return true; 185 | }; 186 | 187 | 188 | let imgError = (image) => { 189 | MesiboLog("imgError"); 190 | image.onerror = ""; 191 | image.src = MESIBO_DEFAULT_PROFILE_IMAGE; 192 | return true; 193 | } 194 | 195 | /*** For debugging purposes only **/ 196 | let getScope = () =>{ 197 | return angular.element(document.getElementById('mesibowebapp')).scope(); 198 | } 199 | 200 | let scrollToEnd = (animate) =>{ 201 | var objDiv = document.getElementById("messages"); 202 | if(!objDiv) 203 | return; 204 | 205 | // MesiboLog("Scroll to last", objDiv, objDiv.scrollTop); 206 | if(animate) 207 | $("#messages").animate({ scrollTop: objDiv.scrollHeight}, 800); 208 | else 209 | objDiv.scrollTop = objDiv.scrollHeight; 210 | 211 | 212 | } 213 | 214 | 215 | 216 | let adjustImageDims = (e)=>{ 217 | 218 | // MesiboLog("adjustImageDims", e); 219 | if(!e) 220 | return; 221 | 222 | 223 | var w = e.width; 224 | var h = e.height; 225 | 226 | 227 | if(!(w && h)) 228 | return; 229 | 230 | var ar = w/h; 231 | 232 | if(ar < 1){ 233 | // MesiboLog("adjustImageDims", e.style.maxWidth); 234 | e.style.objectFit = "cover"; 235 | } 236 | 237 | } 238 | 239 | 240 | let adjustVideoDims = (e)=>{ 241 | 242 | // MesiboLog("adjustVideoDims", e); 243 | if(!e) 244 | return; 245 | 246 | var w = e.videoWidth; 247 | var h = e.videoHeight; 248 | 249 | if(!(w && h)) 250 | return; 251 | 252 | 253 | var ar = w/h; 254 | 255 | if(ar < 1){ 256 | // MesiboLog("adjustImageDims", e.style.maxWidth); 257 | e.style.objectFit = "cover"; 258 | } 259 | } 260 | 261 | -------------------------------------------------------------------------------- /styles/messenger.css: -------------------------------------------------------------------------------- 1 | 2 | ::-webkit-scrollbar { 3 | width: 10px; 4 | } 5 | 6 | ::-webkit-scrollbar-track { 7 | background: none; 8 | } 9 | 10 | ::-webkit-scrollbar-thumb { 11 | background: grey; 12 | } 13 | ::-webkit-scrollbar-thumb:hover { 14 | background: rgba(0, 0, 0, 0.3); 15 | } 16 | 17 | #main-container { 18 | width: 100vw; 19 | height: 100vh; 20 | } 21 | 22 | #navbar { 23 | background: #00868b; 24 | min-height: 72px; 25 | } 26 | 27 | .dropdown-toggle::after { 28 | display: none; 29 | } 30 | 31 | .chat-list-item { 32 | background: white; 33 | cursor: pointer; 34 | } 35 | 36 | .chat-list-item:hover { 37 | background: hsl(0, 0%, 95%); 38 | } 39 | 40 | .chat-list-item:active { 41 | background: hsl(0, 0%, 85%); 42 | } 43 | 44 | .chat-list-item:focus { 45 | background: hsl(0, 0%, 90%); 46 | outline: none; 47 | } 48 | 49 | .chat-list-item .chat-details { 50 | width: 60%; 51 | } 52 | 53 | .chat-list-item.unread .name, 54 | .chat-list-item.unread .last-message { 55 | font-weight: bold; 56 | } 57 | 58 | .chat-list-item .last-message, 59 | #message-area #navbar #details { 60 | white-space: nowrap; 61 | overflow: hidden; 62 | text-overflow: ellipsis; 63 | } 64 | 65 | #message-area { 66 | border-left: 1px solid white; 67 | } 68 | 69 | #message-area .overlay { 70 | background: hsl(0, 0%, 80%); 71 | } 72 | 73 | #input-area { 74 | background: hsl(0, 0%, 95%); 75 | } 76 | 77 | #input-area #input { 78 | outline: none; 79 | } 80 | 81 | #loader {display:none;} 82 | 83 | .message-item { 84 | position:relative; 85 | max-width: 75%; 86 | word-break: break-word; 87 | border-radius:5px !important; 88 | } 89 | .message-item.self { 90 | background: #d5f5ec!important; 91 | } 92 | .message-item .number { 93 | color: #1f7aec !important; 94 | } 95 | .message-item .options { 96 | position: absolute; 97 | top: 0; 98 | right: -3px; 99 | opacity: 0; 100 | transition: all .2s ease-in-out; 101 | -moz-transition: all .2s ease-in-out; 102 | -webkit-transition: all .2s ease-in-out; 103 | } 104 | .message-item:hover .options { 105 | opacity: 1; 106 | right: 0; 107 | } 108 | 109 | #messages { 110 | flex: 1!important; 111 | background: #ECE5DD; 112 | overflow: auto; 113 | } 114 | 115 | .message-title { 116 | color: #333 !important; 117 | text-decoration: none; 118 | } 119 | 120 | .message-subtitle { 121 | color: #555 !important; 122 | text-decoration: none; 123 | } 124 | 125 | #available-users-panel { 126 | position: absolute; 127 | top: 0; 128 | left: -110%; 129 | background: hsl(0, 0%, 95%); 130 | transition: all 0.2s ease-in; 131 | -moz-transition: all .2s ease-in-out; 132 | -webkit-transition: all .2s ease-in-out; 133 | } 134 | 135 | #profile-pic { 136 | cursor: pointer; 137 | position: relative; 138 | width: 100%; 139 | } 140 | 141 | .profile-input { 142 | border-bottom: 2px solid transparent !important; 143 | outline: none; 144 | } 145 | 146 | .profile-input:focus { 147 | border-bottom-color: hsl(0, 0%, 50%) !important; 148 | } 149 | 150 | .overlay { 151 | position: absolute; 152 | top: 0; 153 | left: 0; 154 | z-index: 99; 155 | } 156 | 157 | .videoInsert { 158 | position: absolute; 159 | right: 0; 160 | bottom: 0; 161 | min-width: 100%; 162 | min-height: 100%; 163 | width: auto; 164 | height: auto; 165 | background-size: cover; 166 | overflow: hidden; 167 | } 168 | 169 | .local-video-holder{ 170 | position:fixed; 171 | bottom: 0; 172 | right: 0; 173 | margin:0; 174 | width:200px; 175 | height:150px; 176 | overflow: hidden; 177 | } 178 | 179 | .local-video-holder video{ 180 | position: absolute; 181 | width: 200px !important; 182 | height: auto !important; 183 | transition: 1s opacity; 184 | z-index: 100; 185 | } 186 | 187 | .remote-video-holder{ 188 | position:fixed; 189 | margin:0; 190 | width: 450px; 191 | height: 550px; 192 | overflow: hidden; 193 | } 194 | 195 | .remote-video-holder video{ 196 | position: absolute; 197 | width: 450px !important; 198 | height: auto !important; 199 | transition: 1s opacity; 200 | z-index: -1; 201 | } 202 | 203 | .image-holder{ 204 | width: 250px; 205 | overflow: hidden; 206 | } 207 | 208 | 209 | 210 | .message-list { 211 | flex: 1 1 0; 212 | display: flex; 213 | flex-direction: column; 214 | overflow-y: hidden; 215 | } 216 | 217 | input::-webkit-inner-spin-button { 218 | -webkit-appearance: none; 219 | margin: 0; 220 | } 221 | 222 | /* Firefox */ 223 | input[type=number] { 224 | -moz-appearance: textfield; 225 | } 226 | 227 | .dont-break-out { 228 | 229 | /* These are technically the same, but use both */ 230 | overflow-wrap: break-word; 231 | word-wrap: break-word; 232 | 233 | -ms-word-break: break-all; 234 | /* This is the dangerous one in WebKit, as it breaks things wherever */ 235 | word-break: break-all; 236 | /* Instead use this non-standard one: */ 237 | word-break: break-word; 238 | 239 | /* Adds a hyphen where the word breaks, if supported (No Blink) */ 240 | -ms-hyphens: auto; 241 | -moz-hyphens: auto; 242 | -webkit-hyphens: auto; 243 | hyphens: auto; 244 | 245 | } 246 | -------------------------------------------------------------------------------- /styles/popup.css: -------------------------------------------------------------------------------- 1 | 2 | .popup img { 3 | width: 53px; 4 | height: 53px; 5 | padding-top: 10px; 6 | 7 | } 8 | .bot_logo_container { 9 | display: flex; 10 | } 11 | 12 | #Smallchat .Messages, #Smallchat .Messages_list { 13 | -webkit-box-flex: 1; 14 | -webkit-flex-grow: 1; 15 | -ms-flex-positive: 1; 16 | flex-grow: 1; 17 | } 18 | .chat_features{ 19 | cursor:pointer; 20 | color: #fff; 21 | font-size:16px; 22 | position: absolute; 23 | right: 12px; 24 | z-index: 9; 25 | } 26 | .chat_on { 27 | position: fixed; 28 | z-index: 10; 29 | width: 90px; 30 | height: 90px; 31 | right: 15px; 32 | bottom:20px; 33 | background-color:#00868b; 34 | color: #fff; 35 | border-radius: 50%; 36 | text-align: center; 37 | padding: 9px; 38 | box-shadow: 0 2px 4px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)!important; 39 | cursor: pointer; 40 | display: block; 41 | 42 | } 43 | .chat_on_icon{ 44 | color:#fff; 45 | font-size:25px; 46 | text-align:center; 47 | 48 | } 49 | /* 50 | #Smallchat,#Smallchat * { 51 | box-sizing:border-box; 52 | -webkit-font-smoothing:antialiased; 53 | -moz-osx-font-smoothing:grayscale; 54 | -webkit-tap-highlight-color:transparent 55 | } 56 | */ 57 | #Smallchat .Layout { 58 | pointer-events:auto; 59 | box-sizing:content-box!important; 60 | z-index:999999999; 61 | position:fixed; 62 | bottom:20px; 63 | min-width:50px; 64 | max-width:300px; 65 | max-height:30px; 66 | display:-webkit-box; 67 | display:-webkit-flex; 68 | display:-ms-flexbox; 69 | display:flex; 70 | -webkit-box-orient:vertical; 71 | -webkit-box-direction:normal; 72 | -webkit-flex-direction:column; 73 | -ms-flex-direction:column; 74 | flex-direction:column; 75 | -webkit-box-pack:end; 76 | -webkit-justify-content:flex-end; 77 | -ms-flex-pack:end; 78 | justify-content:flex-end; 79 | border-radius:50px; 80 | box-shadow:5px 0 20px 5px rgba(0,0,0,.1); 81 | -webkit-animation:appear .15s cubic-bezier(.25,.25,.5,1.1); 82 | animation:appear .15s cubic-bezier(.25,.25,.5,1.1); 83 | -webkit-animation-fill-mode:forwards; 84 | animation-fill-mode:forwards; 85 | opacity:0; 86 | -webkit-transition:right .1s cubic-bezier(.25,.25,.5,1),bottom .1s cubic-bezier(.25,.25,.5,1),min-width .2s cubic-bezier(.25,.25,.5,1),max-width .2s cubic-bezier(.25,.25,.5,1),min-height .2s cubic-bezier(.25,.25,.5,1),max-height .2s cubic-bezier(.25,.25,.5,1),border-radius 50ms cubic-bezier(.25,.25,.5,1) .15s,background-color 50ms cubic-bezier(.25,.25,.5,1) .15s,color 50ms cubic-bezier(.25,.25,.5,1) .15s; 87 | transition:right .1s cubic-bezier(.25,.25,.5,1),bottom .1s cubic-bezier(.25,.25,.5,1),min-width .2s cubic-bezier(.25,.25,.5,1),max-width .2s cubic-bezier(.25,.25,.5,1),min-height .2s cubic-bezier(.25,.25,.5,1),max-height .2s cubic-bezier(.25,.25,.5,1),border-radius 50ms cubic-bezier(.25,.25,.5,1) .15s,background-color 50ms cubic-bezier(.25,.25,.5,1) .15s,color 50ms cubic-bezier(.25,.25,.5,1) .15s 88 | 89 | } 90 | 91 | #Smallchat .Layout-right { 92 | right:20px 93 | } 94 | 95 | #Smallchat .Layout-open { 96 | overflow:hidden; 97 | min-width:300px; 98 | max-width:300px; 99 | height:500px; 100 | max-height:500px; 101 | border-radius:10px; 102 | color:#fff; 103 | -webkit-transition:right .1s cubic-bezier(.25,.25,.5,1),bottom .1s cubic-bezier(.25,.25,.5,1.1),min-width .2s cubic-bezier(.25,.25,.5,1.1),max-width .2s cubic-bezier(.25,.25,.5,1.1),max-height .2s cubic-bezier(.25,.25,.5,1.1),min-height .2s cubic-bezier(.25,.25,.5,1.1),border-radius 0ms cubic-bezier(.25,.25,.5,1.1),background-color 0ms cubic-bezier(.25,.25,.5,1.1),color 0ms cubic-bezier(.25,.25,.5,1.1); 104 | transition:right .1s cubic-bezier(.25,.25,.5,1),bottom .1s cubic-bezier(.25,.25,.5,1.1),min-width .2s cubic-bezier(.25,.25,.5,1.1),max-width .2s cubic-bezier(.25,.25,.5,1.1),max-height .2s cubic-bezier(.25,.25,.5,1.1),min-height .2s cubic-bezier(.25,.25,.5,1.1),border-radius 0ms cubic-bezier(.25,.25,.5,1.1),background-color 0ms cubic-bezier(.25,.25,.5,1.1),color 0ms cubic-bezier(.25,.25,.5,1.1); 105 | } 106 | 107 | #Smallchat .Layout-expand { 108 | height:500px; 109 | min-height:500px; 110 | display:none; 111 | } 112 | #Smallchat .Layout-mobile { 113 | bottom:10px 114 | } 115 | #Smallchat .Layout-mobile.Layout-open { 116 | width:calc(100% - 20px); 117 | min-width:calc(100% - 20px) 118 | } 119 | #Smallchat .Layout-mobile.Layout-expand { 120 | bottom:0; 121 | height:100%; 122 | min-height:100%; 123 | width:100%; 124 | min-width:100%; 125 | border-radius:0!important 126 | } 127 | @-webkit-keyframes appear { 128 | 0% { 129 | opacity:0; 130 | -webkit-transform:scale(0); 131 | transform:scale(0) 132 | } 133 | to { 134 | opacity:1; 135 | -webkit-transform:scale(1); 136 | transform:scale(1) 137 | } 138 | } 139 | @keyframes appear { 140 | 0% { 141 | opacity:0; 142 | -webkit-transform:scale(0); 143 | transform:scale(0) 144 | } 145 | to { 146 | opacity:1; 147 | -webkit-transform:scale(1); 148 | transform:scale(1) 149 | } 150 | } 151 | #Smallchat .Messenger_messenger { 152 | position:relative; 153 | height:100%; 154 | width:100%; 155 | /*width:500px;*/ 156 | -webkit-box-orient:vertical; 157 | -webkit-box-direction:normal; 158 | -webkit-flex-direction:column; 159 | -ms-flex-direction:column; 160 | flex-direction:column 161 | } 162 | #Smallchat .Messenger_header,#Smallchat .Messenger_messenger { 163 | display:-webkit-box; 164 | display:-webkit-flex; 165 | display:-ms-flexbox; 166 | display:flex 167 | } 168 | #Smallchat .Messenger_header { 169 | -webkit-box-align:center; 170 | -webkit-align-items:center; 171 | -ms-flex-align:center; 172 | align-items:center; 173 | padding-left:10px; 174 | padding-right:40px; 175 | height:60px; 176 | -webkit-flex-shrink:0; 177 | -ms-flex-negative:0; 178 | flex-shrink:0 179 | } 180 | 181 | 182 | #Smallchat .Messenger_header h4 { 183 | opacity:0; 184 | font-size:12px; 185 | -webkit-animation:slidein .15s .3s; 186 | animation:slidein .15s .3s; 187 | -webkit-animation-fill-mode:forwards; 188 | animation-fill-mode:forwards 189 | } 190 | 191 | #Smallchat .Messenger_prompt { 192 | margin:0; 193 | font-size:20 px; 194 | line-height:30 px; 195 | font-weight:600; 196 | overflow:hidden; 197 | white-space:nowrap; 198 | text-overflow:ellipsis 199 | } 200 | 201 | @-webkit-keyframes slidein { 202 | 0% { 203 | opacity:0; 204 | -webkit-transform:translateX(10px); 205 | transform:translateX(10px) 206 | } 207 | to { 208 | opacity:1; 209 | -webkit-transform:translateX(0); 210 | transform:translateX(0) 211 | } 212 | } 213 | @keyframes slidein { 214 | 0% { 215 | opacity:0; 216 | -webkit-transform:translateX(10px); 217 | transform:translateX(10px) 218 | } 219 | to { 220 | opacity:1; 221 | -webkit-transform:translateX(0); 222 | transform:translateX(0) 223 | } 224 | } 225 | #Smallchat .Messenger_content { 226 | height: 450px; 227 | -webkit-box-flex: 1; 228 | -webkit-flex-grow: 1; 229 | -ms-flex-positive: 1; 230 | flex-grow: 1; 231 | display: -webkit-box; 232 | display: -webkit-flex; 233 | display: -ms-flexbox; 234 | display: flex; 235 | -webkit-box-orient: vertical; 236 | -webkit-box-direction: normal; 237 | -webkit-flex-direction: column; 238 | -ms-flex-direction: column; 239 | flex-direction: column; 240 | background-color: #fff; 241 | } 242 | #Smallchat .Messages { 243 | position: relative; 244 | -webkit-flex-shrink: 1; 245 | -ms-flex-negative: 1; 246 | flex-shrink: 1; 247 | display: -webkit-box; 248 | display: -webkit-flex; 249 | display: -ms-flexbox; 250 | display: flex; 251 | -webkit-box-orient: vertical; 252 | -webkit-box-direction: normal; 253 | -webkit-flex-direction: column; 254 | -ms-flex-direction: column; 255 | flex-direction: column; 256 | overflow-x: hidden; 257 | overflow-y: auto; 258 | padding: 20px; 259 | background-color: #fff; 260 | -webkit-overflow-scrolling: touch; 261 | } 262 | 263 | #Smallchat .Input { 264 | position: relative; 265 | width: 100%; 266 | -webkit-box-flex: 0; 267 | -webkit-flex-grow: 0; 268 | -ms-flex-positive: 0; 269 | flex-grow: 0; 270 | -webkit-flex-shrink: 0; 271 | -ms-flex-negative: 0; 272 | flex-shrink: 0; 273 | padding-top: 17px; 274 | padding-bottom: 15px; 275 | color: #96aab4; 276 | background-color: #fff; 277 | border-top: 1px solid #e6ebea; 278 | } 279 | #Smallchat .Input-blank .Input_field { 280 | max-height: 20px; 281 | color : #808080; 282 | } 283 | #Smallchat .Input_field { 284 | width: 100%; 285 | resize: none; 286 | border: none; 287 | outline: none; 288 | padding: 0; 289 | padding-right: 0px; 290 | padding-left: 0px; 291 | padding-left: 20px; 292 | padding-right: 75px; 293 | background-color: transparent; 294 | font-size: 16px; 295 | line-height: 20px; 296 | min-height: 20px !important; 297 | } 298 | #Smallchat .Input_button-emoji { 299 | right: 45px; 300 | } 301 | #Smallchat .Input_button { 302 | position: absolute; 303 | bottom: 15px; 304 | width: 25px; 305 | height: 25px; 306 | padding: 0; 307 | border: none; 308 | outline: none; 309 | background-color: transparent; 310 | cursor: pointer; 311 | } 312 | #Smallchat .Input_button-send { 313 | right: 15px; 314 | } 315 | #Smallchat .Input-emoji .Input_button-emoji .Icon, #Smallchat .Input_button:hover .Icon { 316 | -webkit-transform: scale(1.1); 317 | -ms-transform: scale(1.1); 318 | transform: scale(1.1); 319 | -webkit-transition: all .1s ease-in-out; 320 | transition: all .1s ease-in-out; 321 | } 322 | #Smallchat .Input-emoji .Input_button-emoji .Icon path, #Smallchat .Input_button:hover .Icon path { 323 | fill: #2c2c46; 324 | } 325 | 326 | /*Imported*/ 327 | .videoInsert { 328 | position: absolute; 329 | right: 0; 330 | bottom: 0; 331 | min-width: 100%; 332 | min-height: 100%; 333 | width: auto; 334 | height: auto; 335 | background-size: cover; 336 | overflow: hidden; 337 | } 338 | 339 | .local-video-holder{ 340 | position:fixed; 341 | bottom: 0; 342 | right: 0; 343 | margin:0; 344 | width:200px; 345 | height:150px; 346 | overflow: hidden; 347 | } 348 | 349 | .local-video-holder video{ 350 | position: absolute; 351 | width: 200px !important; 352 | height: auto !important; 353 | transition: 1s opacity; 354 | z-index: 100; 355 | } 356 | 357 | .remote-video-holder{ 358 | position:fixed; 359 | margin:0; 360 | width: 450px; 361 | height: 550px; 362 | overflow: hidden; 363 | } 364 | 365 | .remote-video-holder video{ 366 | position: absolute; 367 | width: 450px !important; 368 | height: auto !important; 369 | transition: 1s opacity; 370 | z-index: -1; 371 | } 372 | 373 | .image-holder{ 374 | max-width: 250px; 375 | overflow: hidden; 376 | } 377 | 378 | .image-holder img{ 379 | max-width:100%; 380 | max-height:auto; 381 | } 382 | 383 | .file-preview-holder{ 384 | max-width: 300px; 385 | } 386 | 387 | .file-preview-holder img{ 388 | max-width: 100%; 389 | max-height: auto; 390 | } 391 | 392 | -------------------------------------------------------------------------------- /styles/popupdesign.css: -------------------------------------------------------------------------------- 1 | /*---------chat window---------------*/ 2 | .container{ 3 | max-width:900px; 4 | 5 | } 6 | 7 | .date_header{ 8 | /*background-color:#add8e6; */ 9 | text-align: center; 10 | /*border-radius: 25px;*/ 11 | color: #808080; 12 | font-size: 14px; 13 | margin: 0 auto; 14 | /*width: 15%*/ 15 | 16 | } 17 | 18 | .inbox_people { 19 | background: #fff; 20 | float: left; 21 | overflow: hidden; 22 | width: 30%; 23 | border-right: 1px solid #ddd; 24 | } 25 | 26 | .inbox_msg { 27 | border: 1px solid #ddd; 28 | clear: both; 29 | overflow: hidden; 30 | } 31 | 32 | .top_spac { 33 | margin: 20px 0 0; 34 | } 35 | 36 | .recent_heading { 37 | float: left; 38 | width: 40%; 39 | } 40 | 41 | .srch_bar { 42 | display: inline-block; 43 | text-align: right; 44 | width: 60%; 45 | padding: 46 | } 47 | 48 | .headind_srch { 49 | padding: 10px 29px 10px 20px; 50 | overflow: hidden; 51 | border-bottom: 1px solid #c4c4c4; 52 | } 53 | 54 | .recent_heading h4 { 55 | color: #0465ac; 56 | font-size: 16px; 57 | margin: auto; 58 | line-height: 29px; 59 | } 60 | 61 | .srch_bar input { 62 | outline: none; 63 | border: 1px solid #cdcdcd; 64 | border-width: 0 0 1px 0; 65 | width: 80%; 66 | padding: 2px 0 4px 6px; 67 | background: none; 68 | } 69 | 70 | .srch_bar .input-group-addon button { 71 | background: rgba(0, 0, 0, 0) none repeat scroll 0 0; 72 | border: medium none; 73 | padding: 0; 74 | color: #707070; 75 | font-size: 18px; 76 | } 77 | 78 | .srch_bar .input-group-addon { 79 | margin: 0 0 0 -27px; 80 | } 81 | 82 | .chat_ib h5 { 83 | font-size: 15px; 84 | color: #464646; 85 | margin: 0 0 8px 0; 86 | } 87 | 88 | .chat_ib h5 span { 89 | font-size: 13px; 90 | float: right; 91 | } 92 | 93 | .chat_ib p { 94 | font-size: 12px; 95 | color: #989898; 96 | margin: auto; 97 | display: inline-block; 98 | white-space: nowrap; 99 | overflow: hidden; 100 | text-overflow: ellipsis; 101 | } 102 | 103 | .chat_img { 104 | float: left; 105 | width: 11%; 106 | } 107 | 108 | .chat_img img { 109 | width: 100% 110 | } 111 | 112 | .chat_ib { 113 | float: left; 114 | padding: 0 0 0 15px; 115 | width: 88%; 116 | } 117 | 118 | .chat_people { 119 | overflow: hidden; 120 | clear: both; 121 | } 122 | 123 | .chat_list { 124 | border-bottom: 1px solid #ddd; 125 | margin: 0; 126 | padding: 18px 16px 10px; 127 | } 128 | 129 | .inbox_chat { 130 | height: 550px; 131 | overflow-y: scroll; 132 | } 133 | 134 | .active_chat { 135 | background: #e8f6ff; 136 | } 137 | 138 | .received_msg { 139 | display: inline-block; 140 | padding: 0 0 0 10px; 141 | vertical-align: top; 142 | max-width: 70%; 143 | min-width: 40%; 144 | 145 | background: #ebebeb none repeat scroll 0 0; 146 | border-radius: 7px 7px 7px 7px; 147 | color: #646464; 148 | font-size: 14px; 149 | margin: 0; 150 | padding: 5px 10px 5px 12px; 151 | display: inline-block; 152 | word-wrap: break-word; 153 | white-space: normal 154 | } 155 | 156 | .status_msg_img{ 157 | 158 | display: inline-block; 159 | position: relative; 160 | float: right; 161 | width: 20px; 162 | } 163 | 164 | .time_date { 165 | color: #747474; 166 | display: inline-block; 167 | float: right; 168 | font-size: 12px; 169 | padding-right:3px; 170 | 171 | 172 | } 173 | 174 | .time_date_l{ 175 | color: #747474; 176 | float: left; 177 | font-size: 12px; 178 | margin: 2px 0 0; 179 | padding-right:3px; 180 | padding-bottom: 20px 181 | 182 | } 183 | 184 | .received_withd_msg { 185 | width: 57%; 186 | } 187 | 188 | .mesgs{ 189 | float: left; 190 | padding: 30px 15px 0 25px; 191 | width:70%; 192 | } 193 | 194 | .sent_msg { 195 | float: right; 196 | max-width: 70%; 197 | min-width: 40%; 198 | /*background:#dcf8c6; */ 199 | background: #d5f5ec; 200 | /*display: block;*/ 201 | border-radius: 7px 7px 7px 7px; 202 | font-size: 14px; 203 | margin: 0; 204 | padding: 5px 10px 5px 12px; 205 | color: #646464; 206 | 207 | } 208 | 209 | .outgoing_msg { 210 | overflow: hidden; 211 | padding-bottom: 4px; 212 | } 213 | 214 | .incoming_msg { 215 | overflow: hidden; 216 | padding-bottom: 4px; 217 | } 218 | 219 | 220 | .input_msg_write input { 221 | background: rgba(0, 0, 0, 0) none repeat scroll 0 0; 222 | border: medium none; 223 | color: #4c4c4c; 224 | font-size: 15px; 225 | min-height: 48px; 226 | width: 100%; 227 | outline:none; 228 | } 229 | 230 | .type_msg { 231 | border-top: 1px solid #c4c4c4; 232 | position: relative; 233 | } 234 | 235 | .msg_send_btn { 236 | background: #00868b none repeat scroll 0 0; 237 | border:none; 238 | border-radius: 50%; 239 | color: #fff; 240 | cursor: pointer; 241 | font-size: 15px; 242 | height: 33px; 243 | position: absolute; 244 | right: 0; 245 | top: 11px; 246 | width: 33px; 247 | } 248 | 249 | .messaging { 250 | padding: 0 0 50px 0; 251 | } 252 | 253 | .msg_history { 254 | height: 516px; 255 | overflow-y: auto; 256 | right: 20px; 257 | } 258 | 259 | .circle { 260 | border-radius: 50%; 261 | 262 | } 263 | .circle img { 264 | border-radius: 50%; 265 | background-size: cover; 266 | } 267 | --------------------------------------------------------------------------------