├── .gitignore ├── .gitattributes ├── preload.js ├── README.md ├── package.json ├── app ├── css │ ├── register.css │ ├── main.css │ ├── call.css │ ├── message.css │ └── history.css ├── register.html ├── js │ ├── register.js │ ├── message.js │ ├── history.js │ └── call.js ├── history.html ├── main.html ├── call.html └── message.html └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | app/js/common.js -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /preload.js: -------------------------------------------------------------------------------- 1 | const { remote } = require('electron'); 2 | 3 | let currWindow = remote.BrowserWindow.getFocusedWindow(); 4 | 5 | window.closeCurrentWindow = function(){ 6 | currWindow.close(); 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Smart Intercom :office: :house: :phone: 2 | 3 | ### App using Electron, WebRTC and Firebase to develop a desktop application :tada: :tada: 4 | 5 | ### How I can run it? 6 | 7 | - :rocket: Run ***'npm install'*** on terminal in project. 8 | - :rocket: Run ***'npm start'*** on terminal in project. 9 | 10 | #### :pencil: Author: WANTED 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Intercom", 3 | "version": "1.0.0", 4 | "description": "An app calling video and send image !!", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "pack": "electron-builder --dir", 9 | "dist": "electron-builder" 10 | }, 11 | "repository": "", 12 | "keywords": [], 13 | "author": "WANTED", 14 | "license": "CC0-1.0", 15 | "devDependencies": { 16 | "electron": "^10.1.6" 17 | }, 18 | "dependencies": { 19 | "electron-builder": "^22.9.1", 20 | "firebase": "^8.0.0", 21 | "fs": "0.0.1-security", 22 | "jquery": "^3.5.1", 23 | "materialize-css": "^1.0.0-rc.2", 24 | "path": "^0.12.7", 25 | "process": "^0.11.10", 26 | "url": "^0.11.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/css/register.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | background-color:white; 4 | max-width: 100%; 5 | padding: 0 20%; 6 | } 7 | 8 | * { 9 | box-sizing: border-box; 10 | } 11 | 12 | /* Add padding to containers */ 13 | .container { 14 | padding: 16px; 15 | background-color: white; 16 | } 17 | 18 | /* Full-width input fields */ 19 | input[type="text"], 20 | input[type="password"] { 21 | width: 100%; 22 | padding: 15px; 23 | margin: 5px 0 22px 0; 24 | display: inline-block; 25 | border: none; 26 | background: #f1f1f1; 27 | } 28 | 29 | input[type="text"]:focus, 30 | input[type="password"]:focus { 31 | background-color: #ddd; 32 | outline: none; 33 | } 34 | 35 | /* Overwrite default styles of hr */ 36 | hr { 37 | border: 1px solid #f1f1f1; 38 | margin-bottom: 25px; 39 | } 40 | 41 | /* Set a style for the submit button */ 42 | .registerbtn { 43 | background-color: #4caf50; 44 | color: white; 45 | padding: 16px 20px; 46 | margin: 8px 0; 47 | border: none; 48 | cursor: pointer; 49 | width: 100%; 50 | opacity: 0.9; 51 | font-weight: 600; 52 | font-size: 15px; 53 | } 54 | 55 | .registerbtn:hover { 56 | opacity: 1; 57 | } 58 | -------------------------------------------------------------------------------- /app/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Arial, Helvetica, sans-serif; 4 | } 5 | 6 | .topnav { 7 | overflow: hidden; 8 | background-color: #333; 9 | } 10 | 11 | .topnav a { 12 | float: left; 13 | display: block; 14 | color: #f2f2f2; 15 | text-align: center; 16 | padding: 14px 16px; 17 | text-decoration: none; 18 | font-size: 17px; 19 | } 20 | 21 | .topnav a:hover { 22 | background-color: #ddd; 23 | color: black; 24 | } 25 | 26 | .topnav a.active { 27 | background-color: #4caf50; 28 | color: white; 29 | } 30 | 31 | .topnav a.exit { 32 | background-color: #ff0000; 33 | color: white; 34 | } 35 | 36 | .topnav .icon { 37 | display: none; 38 | } 39 | 40 | @media screen and (max-width: 600px) { 41 | .topnav a:not(:first-child) { 42 | display: none; 43 | } 44 | .topnav a.icon { 45 | float: right; 46 | display: block; 47 | } 48 | } 49 | 50 | @media screen and (max-width: 600px) { 51 | .topnav.responsive { 52 | position: relative; 53 | } 54 | .topnav.responsive .icon { 55 | position: absolute; 56 | right: 0; 57 | top: 0; 58 | } 59 | .topnav.responsive a { 60 | float: none; 61 | display: block; 62 | text-align: left; 63 | } 64 | } 65 | 66 | #call, 67 | #history, 68 | #notification, 69 | #statistic, 70 | #signup { 71 | display: none; 72 | } 73 | #call:target { 74 | display: block; 75 | } 76 | #history:target { 77 | display: block; 78 | } 79 | #notification:target { 80 | display: block; 81 | } 82 | #statistic:target { 83 | display: block; 84 | } 85 | #signup:target { 86 | display: block; 87 | } 88 | -------------------------------------------------------------------------------- /app/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

Đăng Ký Thành Viên

10 |

Hãy điền đầy đủ thông tin dưới đây.

11 |
12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const url = require('url'); 3 | const path = require('path'); 4 | 5 | const { app, BrowserWindow, Menu } = electron; 6 | 7 | let mainWindow; 8 | 9 | 10 | app.on('ready', function () { 11 | mainWindow = new BrowserWindow({ 12 | width: 1600, 13 | height: 900, 14 | webPreferences: { 15 | nodeIntegration: true, 16 | allowRunningInsecureContent: true, 17 | enableRemoteModule: true, 18 | preload: path.join(app.getAppPath(), 'preload.js') 19 | } 20 | }); 21 | 22 | mainWindow.setMenuBarVisibility(false); 23 | 24 | mainWindow.loadURL(url.format({ 25 | pathname: path.join(__dirname, './app/main.html'), 26 | protocol: 'file:', 27 | slashes: true 28 | })); 29 | 30 | 31 | 32 | mainWindow.on('closed', function () { 33 | app.quit(); 34 | }) 35 | 36 | //build menu from template 37 | 38 | const mainMenu = Menu.buildFromTemplate(mainMenuTemplate); 39 | Menu.setApplicationMenu(mainMenu); 40 | }); 41 | 42 | const mainMenuTemplate = [ 43 | ] 44 | 45 | if (process.platform == 'darwin') { 46 | mainMenuTemplate.unshift({}); 47 | } 48 | 49 | //add developer tools 50 | if (process.env.NODE_ENV != 'production') { 51 | mainMenuTemplate.push({ 52 | label: 'Developer Tools', 53 | submenu: [ 54 | { 55 | label: 'Toggle DevTools', 56 | accelerator: process.platform == 'darwin' ? 'Command+I' : 'Ctrl+I', 57 | click(item, focusedWindow) { 58 | focusedWindow.toggleDevTools(); 59 | } 60 | }, 61 | { 62 | role: 'reload' 63 | } 64 | ] 65 | }) 66 | } -------------------------------------------------------------------------------- /app/js/register.js: -------------------------------------------------------------------------------- 1 | firebase.initializeApp(config); 2 | const db = firebase.firestore(); 3 | 4 | function validateForm(email, psw, psw_repeat, phone, dept) { 5 | if ( 6 | email.length == 0 || 7 | psw.toString().length < 6 || 8 | phone.length == 0 || 9 | dept.length == 0 10 | ) { 11 | alert( 12 | "Nhập thông tin không hợp lệ! Email phải có @, Mật khẩu phải có ít nhất 6 kí tự." 13 | ); 14 | return false; 15 | } else if (psw != psw_repeat) { 16 | alert("Mật khẩu không khớp!"); 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | 23 | async function register() { 24 | var email = document.getElementById("email").value; 25 | var psw = document.getElementById("psw").value; 26 | var psw_repeat = document.getElementById("psw-repeat").value; 27 | var phone = document.getElementById("phone").value; 28 | var dept = document.getElementById("dept").value; 29 | 30 | if (validateForm(email, psw, psw_repeat, phone, dept)) { 31 | firebase 32 | .auth() 33 | .createUserWithEmailAndPassword(email, psw) 34 | .then(async (user) => { 35 | const res = await db.collection("users").doc(user.user.uid).set({ 36 | id: user.user.uid, 37 | email: email, 38 | dept: dept, 39 | key: "VINH", 40 | token: "", 41 | notifications: true, 42 | urlToImage: 43 | "https://avatars2.githubusercontent.com/u/60530946?s=460&u=e342f079ed3571122e21b42eedd0ae251a9d91ce&v=4", 44 | username: email.substring(0, email.length - 10), 45 | phone: phone, 46 | }); 47 | 48 | alert("Đăng kí thành công!"); 49 | window.location.reload(); 50 | }) 51 | .catch((error) => { 52 | alert(error.message); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/history.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | History 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 | 40 |
41 |
42 | Lọc 43 |
44 | 46 | 47 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 |
12 | Liên lạc 15 | Thông báo 16 | Lịch sử 17 | Thống kê 18 | Đăng ký 19 | Thoát 22 |
23 | 24 |
25 |
26 | 32 |
33 |
34 | 40 |
41 |
42 | 48 |
49 |
50 | 51 |
52 | 58 |
59 |
60 | 61 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/css/call.css: -------------------------------------------------------------------------------- 1 | body { 2 | /**/ 3 | max-width: 100%; 4 | } 5 | 6 | .my-video { 7 | background-color: #ddd; 8 | border-radius: 4px; 9 | margin: 30px 0px 0px 30px; 10 | width: 440px; 11 | height: 320px; 12 | object-fit: cover; 13 | } 14 | 15 | .friend-video { 16 | background-color: #ddd; 17 | border-radius: 4px; 18 | margin: 30px 0px 0px 30px; 19 | width: 220px; 20 | height: 320px; 21 | object-fit: cover; 22 | } 23 | 24 | .btn-call { 25 | margin: 5px 0px 0px 30px !important; 26 | height: 54px; 27 | width: 770px; 28 | border-radius: 2.5px !important; 29 | font-size: 18px !important; 30 | font-weight: 500; 31 | color: white; 32 | border: 0cm; 33 | background-color: RoyalBlue; 34 | } 35 | 36 | .btn-call:hover { 37 | background-color: DodgerBlue; 38 | } 39 | 40 | .btn-call:after { 41 | background-color: DodgerBlue; 42 | } 43 | 44 | .right-column .display-container .main-container { 45 | margin-left: 20px; 46 | } 47 | 48 | .right-column .picture-container { 49 | justify-content: center; 50 | align-items: center; 51 | padding-left: 240px; 52 | } 53 | 54 | .right-column .display-container { 55 | display: flex; 56 | margin-bottom: 30px; 57 | } 58 | 59 | .right-column .button { 60 | display: inline-block; 61 | } 62 | 63 | .right-column .btn-display { 64 | width: 300px; 65 | padding: 8px 0px 8px 0px; 66 | margin-left: 34px !important; 67 | text-align: center; 68 | } 69 | 70 | .right-column #image_container { 71 | width: 240px; 72 | height: 220px; 73 | background-color: #ddd; 74 | } 75 | 76 | .right-column canvas { 77 | border: 1px solid #ddd; 78 | background-color: #ddd; 79 | width: 320px; 80 | height: 240px; 81 | overflow: auto; 82 | } 83 | 84 | .container-main { 85 | max-width: 100%; 86 | margin: 0 auto; 87 | display: flex; 88 | } 89 | 90 | /* Columns */ 91 | .left-column { 92 | height: 100vh; 93 | width: 25%; 94 | position: relative; 95 | border-right: 0.2px solid #ddd; 96 | overflow-y: auto; 97 | } 98 | 99 | .right-column { 100 | width: 75%; 101 | overflow: hidden !important; 102 | } 103 | #userItem { 104 | padding: 10px; 105 | } 106 | .circle { 107 | margin-top: 8px; 108 | height: 60px !important; 109 | width: 60px !important; 110 | } 111 | h5 { 112 | margin-left: 90px !important; 113 | } 114 | p { 115 | margin-left: 90px !important; 116 | } 117 | .collection .collection-item.avatar { 118 | border-right: 0 !important; 119 | padding-top: 4px !important; 120 | vertical-align: middle !important; 121 | padding-left: 0 !important; 122 | margin-left: 0 !important; 123 | } 124 | 125 | .column { 126 | float: left; 127 | } 128 | 129 | /* Clear floats after the columns */ 130 | .row:after { 131 | content: ""; 132 | display: table; 133 | clear: both; 134 | } 135 | 136 | .image-upload > input 137 | { 138 | display: none; 139 | } 140 | 141 | .btn-container { 142 | background-color: RoyalBlue; 143 | border: none; 144 | color: white; 145 | padding: 20px; 146 | font-size: 16.5px; 147 | font-display: block; 148 | cursor: pointer; 149 | } 150 | 151 | /* Darker background on mouse-over */ 152 | .btn-container:hover { 153 | background-color: DodgerBlue; 154 | } -------------------------------------------------------------------------------- /app/css/message.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | width: 100%; 5 | margin: 0; 6 | font-family: "Roboto", sans-serif; 7 | } 8 | 9 | .container-main { 10 | max-width: 100%; 11 | margin: 0 auto; 12 | display: flex; 13 | } 14 | 15 | /* Columns */ 16 | .left-column { 17 | height: 100vh; 18 | width: 25%; 19 | position: relative; 20 | border-right: 0.2px solid #ddd; 21 | overflow-y: auto; 22 | } 23 | 24 | .center { 25 | width: 35%; 26 | overflow: hidden !important; 27 | } 28 | 29 | .right-column { 30 | width: 40%; 31 | overflow: hidden !important; 32 | } 33 | #userItem { 34 | padding: 10px; 35 | } 36 | .circle { 37 | margin-top: 8px; 38 | height: 60px !important; 39 | width: 60px !important; 40 | } 41 | h5 { 42 | margin-left: 80px; 43 | } 44 | p { 45 | margin-left: 80px !important; 46 | font-size: 14px !important; 47 | font-weight: 400; 48 | } 49 | .collection .collection-item.avatar { 50 | border-right: 0 !important; 51 | padding-top: 4px !important; 52 | margin-right: 0 !important; 53 | padding-right: 0 !important; 54 | vertical-align: middle !important; 55 | } 56 | .collection .collection-item.avatar p { 57 | margin: 0; 58 | margin-left: 50px; 59 | } 60 | .select-all { 61 | margin-top: 24px !important; 62 | display: flex; 63 | justify-content: space-between; 64 | max-width: 100%; 65 | background: white; 66 | padding: 0 18px; 67 | } 68 | .font-select-all { 69 | font-size: 15px; 70 | font-weight: 600; 71 | color: dodgerblue; 72 | } 73 | 74 | .active { 75 | color:lightseagreen; 76 | } 77 | 78 | .unactive { 79 | color:gray; 80 | } 81 | 82 | .container-mess { 83 | width: 100%; 84 | padding: 32px 40px; 85 | } 86 | .note { 87 | display: flex; 88 | margin-left: 20px; 89 | } 90 | .input-note { 91 | width: 400px; 92 | } 93 | .btn-update { 94 | margin-left: 30px; 95 | } 96 | #canvas { 97 | border: 1.5px solid #ddd; 98 | width: 360px; 99 | height: 270px; 100 | margin-left: 25px; 101 | background-color: #ddd; 102 | } 103 | 104 | .image-upload > input 105 | { 106 | display: none; 107 | } 108 | 109 | .btn-container { 110 | background-color: RoyalBlue; 111 | border: none; 112 | color: white; 113 | padding: 20px; 114 | font-size: 16.5px; 115 | font-display: block; 116 | cursor: pointer; 117 | margin-top: 8px; 118 | margin-left: 30px; 119 | } 120 | 121 | /* Darker background on mouse-over */ 122 | .btn-container:hover { 123 | background-color: DodgerBlue; 124 | } 125 | 126 | .column { 127 | float: left; 128 | padding: 10px; 129 | } 130 | 131 | /* Clear floats after the columns */ 132 | .row:after { 133 | content: ""; 134 | clear: both; 135 | } 136 | 137 | video { 138 | background-color: #ddd; 139 | height: 120px; 140 | width: 120px; 141 | object-fit: cover; 142 | } 143 | 144 | .btn-noti { 145 | margin-top: 20px !important; 146 | height: 54px; 147 | width: 105%; 148 | border-radius: 2.5px !important; 149 | font-size: 14px !important; 150 | font-weight: 600; 151 | color: white; 152 | border: 0cm; 153 | background-color: RoyalBlue; 154 | } 155 | 156 | .btn-noti:hover { 157 | background-color: DodgerBlue; 158 | } 159 | 160 | .cb-container { 161 | margin-top: 20px !important; 162 | } 163 | 164 | input[type=checkbox] { 165 | margin: 4px 0 0; 166 | margin-top: 20px !important; 167 | padding-top: 20px !important; 168 | line-height: normal; 169 | } -------------------------------------------------------------------------------- /app/call.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dashboard 7 | 8 | 12 | 13 | 17 | 21 | 22 | 26 | 27 | 28 | 29 | 30 |
31 | 32 |
33 | 56 | 57 | 58 |
59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 |
67 |
68 |
69 | 70 |
71 |
72 | 75 | 76 | 77 |
78 | 79 | 80 |
81 |
82 | 83 | 93 | 94 |

95 |
96 |
97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /app/message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dashboard 7 | 8 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 57 | 58 |
59 | Gửi Cho Mọi Người 60 | 61 | 65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | 80 | 81 | 82 | 90 | 91 | 95 |
96 |
97 | 98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 |
106 | 107 |
108 | 111 |
112 | 115 | 116 | 122 |
123 |
124 |
125 |
126 |
127 |
128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /app/css/history.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | input { 6 | height: 500px; 7 | } 8 | 9 | .circle { 10 | height: 60px !important; 11 | width: 60px !important; 12 | 13 | } 14 | 15 | .padding-li { 16 | padding-left: 20px !important; 17 | } 18 | 19 | h5 { 20 | padding-left: 30px; 21 | } 22 | 23 | p { 24 | padding-left: 30px; 25 | } 26 | 27 | .li-user { 28 | padding-left: 30px; 29 | } 30 | 31 | .imageSend { 32 | width: 150px; 33 | height: 110px; 34 | margin-right: 10px; 35 | border-radius: 0 !important; 36 | } 37 | 38 | .filter-container { 39 | display: flex; 40 | margin-top: 10px; 41 | } 42 | 43 | .txt-filter { 44 | font-size: 15px; 45 | color: #000; 46 | } 47 | 48 | .input-filter { 49 | width: 150px !important; 50 | } 51 | 52 | .from-filter { 53 | margin-left: 30px; 54 | } 55 | 56 | .to-filter { 57 | margin-left: 30px; 58 | } 59 | 60 | .state-filter { 61 | margin-left: 30px; 62 | } 63 | 64 | .pos-state { 65 | position: relative; 66 | top: 2px; 67 | } 68 | .btn-filter{ 69 | position: relative; 70 | left: 80px; 71 | } 72 | .custom-select { 73 | position: relative; 74 | font-family: Arial; 75 | left: 60px; 76 | bottom: 25px; 77 | } 78 | 79 | .custom-select select { 80 | display: none; 81 | /*hide original SELECT element: */ 82 | 83 | } 84 | 85 | .select-selected { 86 | background-color: #ddd; 87 | } 88 | 89 | /* Style the arrow inside the select element: */ 90 | .select-selected:after { 91 | position: absolute; 92 | content: ""; 93 | top: 14px; 94 | right: 10px; 95 | width: 0; 96 | height: 0; 97 | border: 6px solid transparent; 98 | border-color: #000 transparent transparent transparent; 99 | } 100 | 101 | /* Point the arrow upwards when the select box is open (active): */ 102 | .select-selected.select-arrow-active:after { 103 | border-color: transparent transparent #000 transparent; 104 | top: 7px; 105 | } 106 | 107 | /* style the items (options), including the selected item: */ 108 | .select-items div, 109 | .select-selected { 110 | color: #000; 111 | padding: 8px 16px; 112 | border: 1px solid transparent; 113 | border-color: transparent transparent rgba(0, 0, 0, 0.1) transparent; 114 | cursor: pointer; 115 | } 116 | 117 | /* Style items (options): */ 118 | .select-items { 119 | position: absolute; 120 | background-color: #fff; 121 | top: 100%; 122 | left: 0; 123 | right: 0; 124 | z-index: 99; 125 | } 126 | 127 | /* Hide the items when the select box is closed: */ 128 | .select-hide { 129 | display: none; 130 | } 131 | 132 | .select-items div:hover, 133 | .same-as-selected { 134 | background-color: rgba(0, 0, 0, 0.1); 135 | } 136 | 137 | #myImg { 138 | border-radius: 5px; 139 | cursor: pointer; 140 | transition: 0.3s; 141 | } 142 | 143 | #myImg:hover { 144 | opacity: 0.7; 145 | } 146 | 147 | /* The Modal (background) */ 148 | .modal { 149 | display: none; 150 | /* Hidden by default */ 151 | position: fixed; 152 | /* Stay in place */ 153 | z-index: 1; 154 | /* Sit on top */ 155 | padding-top: 100px; 156 | /* Location of the box */ 157 | left: 0; 158 | top: 0; 159 | width: 100%; 160 | /* Full width */ 161 | height: 100%; 162 | /* Full height */ 163 | overflow: auto; 164 | /* Enable scroll if needed */ 165 | background-color: rgb(0, 0, 0); 166 | /* Fallback color */ 167 | background-color: rgba(0, 0, 0, 0.9); 168 | /* Black w/ opacity */ 169 | } 170 | 171 | /* Modal Content (Image) */ 172 | .modal-content { 173 | margin: auto; 174 | display: block; 175 | width: 80%; 176 | max-width: 700px; 177 | } 178 | 179 | /* Caption of Modal Image (Image Text) - Same Width as the Image */ 180 | #caption { 181 | margin: auto; 182 | display: block; 183 | width: 80%; 184 | max-width: 700px; 185 | text-align: center; 186 | color: #ccc; 187 | padding: 10px 0; 188 | height: 150px; 189 | } 190 | 191 | /* Add Animation - Zoom in the Modal */ 192 | .modal-content, 193 | #caption { 194 | animation-name: zoom; 195 | animation-duration: 0.6s; 196 | } 197 | 198 | @keyframes zoom { 199 | from { 200 | transform: scale(0) 201 | } 202 | 203 | to { 204 | transform: scale(1) 205 | } 206 | } 207 | 208 | /* The Close Button */ 209 | .close { 210 | position: absolute; 211 | top: 15px; 212 | right: 35px; 213 | color: #f1f1f1; 214 | font-size: 40px; 215 | font-weight: bold; 216 | transition: 0.3s; 217 | } 218 | 219 | .close:hover, 220 | .close:focus { 221 | color: #bbb; 222 | text-decoration: none; 223 | cursor: pointer; 224 | } 225 | 226 | /* 100% Image Width on Smaller Screens */ 227 | @media only screen and (max-width: 700px) { 228 | .modal-content { 229 | width: 100%; 230 | } 231 | } -------------------------------------------------------------------------------- /app/js/message.js: -------------------------------------------------------------------------------- 1 | firebase.initializeApp(config); 2 | const db = firebase.firestore(); 3 | const ref = firebase.storage().ref(); 4 | 5 | var users = []; 6 | var file; 7 | var selectAll = false; 8 | var yourVideo = document.getElementById("yourVideo"); 9 | var pc = new RTCPeerConnection(); 10 | 11 | function showMyFace() { 12 | navigator.mediaDevices 13 | .getUserMedia({ audio: true, video: true }) 14 | .then((stream) => (yourVideo.srcObject = stream)) 15 | .then((stream) => pc.addStream(stream)); 16 | } 17 | 18 | function capture() { 19 | var canvas = document.getElementById("canvas"); 20 | var video = document.getElementById("yourVideo"); 21 | canvas.width = video.videoWidth; 22 | canvas.height = video.videoHeight; 23 | canvas 24 | .getContext("2d") 25 | .drawImage(video, 0, 0, video.videoWidth, video.videoHeight); // for drawing the video element on the canvas 26 | 27 | if (canvas.getContext) { 28 | var mySrc = canvas.toDataURL("image/png"); 29 | file = dataURLtoFile(mySrc, "filename.png"); 30 | } 31 | } 32 | 33 | document.getElementById("capture").addEventListener("click", capture); 34 | //db.settings({ timestampsInSnapshots: true}); 35 | //get user from database 36 | const getUser = db 37 | .collection("users") 38 | .where("id", "!=", "FnAWubAdtFgYwGIML23oRAtp5u93") 39 | .get() 40 | .then((snapshot) => { 41 | snapshot.docs.forEach((doc) => { 42 | let items = doc.data(); 43 | 44 | users.push(items); 45 | }); 46 | showUser(); 47 | }); 48 | 49 | function showUser() { 50 | users.forEach((user) => { 51 | var ul = document.querySelector("ul"); 52 | var li = document.createElement("li"); 53 | var label = document.createElement("label"); 54 | var input = document.createElement("input"); 55 | var span = document.createElement("span"); 56 | var img = document.createElement("img"); 57 | var h5 = document.createElement("h5"); 58 | var p = document.createElement("p"); 59 | var a1 = document.createElement("a"); 60 | var a2 = document.createElement("a"); 61 | 62 | li.className = "collection-item avatar"; 63 | li.id = "userItem"; 64 | img.className = "circle"; 65 | h5.className = "title"; 66 | a2.className = "secondary-content icon-call"; 67 | a2.id = "btn-call"; 68 | input.className = "filled-in list-user-item"; 69 | 70 | label.htmlFor = user.id; 71 | input.type = "checkbox"; 72 | input.id = user.id; 73 | label.onclick = "test()"; 74 | 75 | input.name = "user"; 76 | input.value = user.id; 77 | img.src = 78 | user.urlToImage == "" 79 | ? "https://avatars2.githubusercontent.com/u/60530946?s=460&u=e342f079ed3571122e21b42eedd0ae251a9d91ce&v=4" 80 | : user.urlToImage; 81 | h5.textContent = user.username; 82 | p.textContent = "Phòng : "; 83 | a1.textContent = user.dept; 84 | a2.href = "#"; 85 | 86 | label.appendChild(input); 87 | label.appendChild(span); 88 | 89 | a2.appendChild(label); 90 | p.appendChild(a1); 91 | li.appendChild(img); 92 | li.appendChild(h5); 93 | li.appendChild(p); 94 | li.appendChild(a2); 95 | 96 | ul.appendChild(li); 97 | }); 98 | } 99 | function removeDuplicates(array) { 100 | return array.filter((a, b) => array.indexOf(a) === b); 101 | } 102 | 103 | var arrCheckedUser = []; 104 | function selectUser() { 105 | var checks = document.getElementsByClassName("list-user-item"); 106 | arrCheckedUser = []; 107 | for (i = 0; i < users.length; i++) { 108 | if (checks[i].checked === true) { 109 | arrCheckedUser.push(checks[i].value); 110 | } 111 | } 112 | removeDuplicates(arrCheckedUser); 113 | } 114 | 115 | // Select all user 116 | document.getElementById("select-all").onclick = function () { 117 | var checkboxes = document.getElementsByName("user"); 118 | for (var checkbox of checkboxes) { 119 | checkbox.checked = this.checked; 120 | } 121 | checkSelectAll(); 122 | }; 123 | var _checkSelectAll = document.getElementById("select-all"); 124 | function checkSelectAll() { 125 | if (_checkSelectAll.checked) { 126 | selectAll = true; 127 | var current = document.getElementsByClassName("font-select-all"); 128 | current.className = current.className.replace(" active", " unactive"); 129 | } else { 130 | selectAll = false; 131 | } 132 | console.log(selectAll); 133 | } 134 | 135 | //Search user 136 | function searchUser() { 137 | var input, filter, ul, li, a, i, txtValue; 138 | input = document.getElementById("search"); 139 | filter = input.value.toUpperCase(); 140 | ul = document.getElementById("myUL"); 141 | li = ul.getElementsByTagName("li"); 142 | for (i = 0; i < li.length; i++) { 143 | a = li[i].getElementsByTagName("a")[0]; 144 | txtValue = a.textContent || a.innerText; 145 | if (txtValue.toUpperCase().indexOf(filter) > -1) { 146 | li[i].style.display = ""; 147 | } else { 148 | li[i].style.display = "none"; 149 | } 150 | } 151 | } 152 | 153 | async function addDataToFirestore() { 154 | selectUser(); 155 | if (arrCheckedUser.length == 0 && selectAll == false) { 156 | alert("Chưa có người nhận!"); 157 | } else { 158 | if (file == null) { 159 | console.log(arrCheckedUser); 160 | const res = await db.collection("notifications").add({ 161 | all: selectAll, 162 | body: document.getElementById("note-input").value, 163 | key: "VINH", 164 | members: arrCheckedUser, 165 | publishAt: firebase.firestore.FieldValue.serverTimestamp(), 166 | title: document.getElementById("title-input").value, 167 | urlToImage: "", 168 | }); 169 | 170 | document.getElementById("note-input").value = ""; 171 | document.getElementById("title-input").value = ""; 172 | } else { 173 | const name = +new Date() + "-" + file.name; 174 | const task = ref.child("Photos").child(name).put(file); 175 | task 176 | .then((snapshot) => snapshot.ref.getDownloadURL()) 177 | .then(async (url) => { 178 | console.log(url); 179 | const res = await db.collection("notifications").add({ 180 | all: selectAll, 181 | body: document.getElementById("note-input").value, 182 | key: "VINH", 183 | members: arrCheckedUser, 184 | publishAt: firebase.firestore.FieldValue.serverTimestamp(), 185 | title: document.getElementById("title-input").value, 186 | urlToImage: url, 187 | }); 188 | 189 | document.getElementById("note-input").value = ""; 190 | document.getElementById("title-input").value = ""; 191 | var canvas = document.getElementById("canvas"); 192 | const context = canvas.getContext("2d"); 193 | context.clearRect(0, 0, canvas.width, canvas.height); 194 | file = null; 195 | }) 196 | .catch(console.error); 197 | } 198 | } 199 | } 200 | 201 | function dataURLtoFile(dataurl, filename) { 202 | var arr = dataurl.split(","), 203 | mime = arr[0].match(/:(.*?);/)[1], 204 | bstr = atob(arr[1]), 205 | n = bstr.length, 206 | u8arr = new Uint8Array(n); 207 | while (n--) { 208 | u8arr[n] = bstr.charCodeAt(n); 209 | } 210 | return new File([u8arr], filename, { type: mime }); 211 | } 212 | 213 | function readURL(input) { 214 | if (input.files && input.files[0]) { 215 | var reader = new FileReader(); 216 | var image = new Image(); 217 | 218 | reader.onload = function (e) { 219 | image.src = e.target.result; 220 | console.log(image.src); 221 | 222 | var canvas = document.getElementById("canvas"); 223 | var context = canvas.getContext("2d"); 224 | 225 | canvas.getContext("2d").drawImage(image, 0, 0, 300, 300); 226 | 227 | image.onload = function () { 228 | context.drawImage(image, 0, 0, canvas.width, canvas.height); 229 | }; 230 | }; 231 | 232 | reader.readAsDataURL(input.files[0]); 233 | 234 | file = input.files[0]; 235 | console.log(file); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /app/js/history.js: -------------------------------------------------------------------------------- 1 | //set to input value is today 2 | function getDate() { 3 | var today = new Date(); 4 | var dd = today.getDate(); 5 | var mm = today.getMonth() + 1; //January is 0! 6 | var yyyy = today.getFullYear(); 7 | 8 | if (dd < 10) { 9 | dd = "0" + dd; 10 | } 11 | 12 | if (mm < 10) { 13 | mm = "0" + mm; 14 | } 15 | 16 | today = yyyy + "-" + mm + "-" + dd; 17 | console.log(today); 18 | document.getElementById("end").value = today; 19 | } 20 | 21 | window.onload = function () { 22 | getDate(); 23 | }; 24 | 25 | firebase.initializeApp(config); 26 | 27 | const db = firebase.firestore(); 28 | 29 | //db.settings({ timestampsInSnapshots: true}); 30 | //get user from database 31 | const getUser = db 32 | .collection("requests") 33 | .orderBy("responcedTime", "desc") 34 | .get() 35 | .then((snapshot) => { 36 | snapshot.docs.forEach((doc) => { 37 | let items = doc.data(); 38 | 39 | db.collection("users") 40 | .where("id", "==", items.receiveID) 41 | .get() 42 | .then((snap) => { 43 | snap.docs.forEach((element) => { 44 | var username = element.data().username; 45 | var urlOfUser = element.data().urlToImage; 46 | var ul = document.querySelector("ul"); 47 | var li = document.createElement("li"); 48 | var img = document.createElement("img"); 49 | var h5 = document.createElement("h5"); 50 | var p = document.createElement("p"); 51 | var p1 = document.createElement("p"); 52 | var p2 = document.createElement("p"); 53 | var a1 = document.createElement("a"); 54 | var a12 = document.createElement("a"); 55 | var a13 = document.createElement("a"); 56 | var imgSend = document.createElement("img"); 57 | var publishAt = items.publishAt.toDate(); 58 | var responceTime = items.responcedTime.toDate(); 59 | 60 | const options = { 61 | weekday: "long", 62 | year: "numeric", 63 | month: "long", 64 | day: "numeric", 65 | }; 66 | li.className = "collection-item avatar"; 67 | li.id = "userItem"; 68 | img.className = "circle"; 69 | h5.className = "title"; 70 | imgSend.className = "imageSend secondary-content"; 71 | imgSend.id = "myImg"; 72 | imgSend.src = items.urlToImage; 73 | 74 | img.src = urlOfUser; 75 | 76 | //console.log(img.src); 77 | h5.textContent = username; 78 | p.textContent = "Trạng thái: "; 79 | p1.textContent = "Thời gian trả lời: "; 80 | p2.textContent = "Thời gian bắt đầu gọi: "; 81 | a1.textContent = items.responce; 82 | a12.textContent = 83 | responceTime.toLocaleDateString("vi-VN", options) + 84 | " ---- " + 85 | checkValid(responceTime.getHours()) + 86 | ":" + 87 | checkValid(responceTime.getMinutes()) + 88 | ":" + 89 | checkValid(responceTime.getSeconds()); 90 | a13.textContent = 91 | publishAt.toLocaleDateString("vi-VN", options) + 92 | " ---- " + 93 | checkValid(publishAt.getHours()) + 94 | ":" + 95 | checkValid(publishAt.getMinutes()) + 96 | ":" + 97 | checkValid(publishAt.getSeconds()); 98 | 99 | p.appendChild(a1); 100 | p1.appendChild(a12); 101 | p2.appendChild(a13); 102 | li.appendChild(img); 103 | li.appendChild(h5); 104 | li.appendChild(p); 105 | li.appendChild(p1); 106 | li.appendChild(p2); 107 | li.appendChild(imgSend); 108 | 109 | ul.appendChild(li); 110 | }); 111 | }); 112 | }); 113 | }); 114 | 115 | function checkValid(input) { 116 | if (input < 10) { 117 | return "0" + input.toString(); 118 | } else { 119 | return input; 120 | } 121 | } 122 | 123 | //Dropdown filter 124 | var x, i, j, l, ll, selElmnt, a, b, c; 125 | /* Look for any elements with the class "custom-select": */ 126 | x = document.getElementsByClassName("custom-select"); 127 | l = x.length; 128 | for (i = 0; i < l; i++) { 129 | selElmnt = x[i].getElementsByTagName("select")[0]; 130 | ll = selElmnt.length; 131 | /* For each element, create a new DIV that will act as the selected item: */ 132 | a = document.createElement("DIV"); 133 | a.setAttribute("class", "select-selected"); 134 | a.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML; 135 | x[i].appendChild(a); 136 | /* For each element, create a new DIV that will contain the option list: */ 137 | b = document.createElement("DIV"); 138 | b.setAttribute("class", "select-items select-hide"); 139 | for (j = 1; j < ll; j++) { 140 | /* For each option in the original select element, 141 | create a new DIV that will act as an option item: */ 142 | c = document.createElement("DIV"); 143 | c.innerHTML = selElmnt.options[j].innerHTML; 144 | c.addEventListener("click", function (e) { 145 | /* When an item is clicked, update the original select box, 146 | and the selected item: */ 147 | var y, i, k, s, h, sl, yl; 148 | s = this.parentNode.parentNode.getElementsByTagName("select")[0]; 149 | sl = s.length; 150 | h = this.parentNode.previousSibling; 151 | for (i = 0; i < sl; i++) { 152 | if (s.options[i].innerHTML == this.innerHTML) { 153 | s.selectedIndex = i; 154 | h.innerHTML = this.innerHTML; 155 | y = this.parentNode.getElementsByClassName("same-as-selected"); 156 | yl = y.length; 157 | for (k = 0; k < yl; k++) { 158 | y[k].removeAttribute("class"); 159 | } 160 | this.setAttribute("class", "same-as-selected"); 161 | break; 162 | } 163 | } 164 | h.click(); 165 | }); 166 | b.appendChild(c); 167 | } 168 | x[i].appendChild(b); 169 | a.addEventListener("click", function (e) { 170 | /* When the select box is clicked, close any other select boxes, 171 | and open/close the current select box: */ 172 | e.stopPropagation(); 173 | closeAllSelect(this); 174 | this.nextSibling.classList.toggle("select-hide"); 175 | this.classList.toggle("select-arrow-active"); 176 | }); 177 | } 178 | 179 | function closeAllSelect(elmnt) { 180 | /* A function that will close all select boxes in the document, 181 | except the current select box: */ 182 | var x, 183 | y, 184 | i, 185 | xl, 186 | yl, 187 | arrNo = []; 188 | x = document.getElementsByClassName("select-items"); 189 | y = document.getElementsByClassName("select-selected"); 190 | xl = x.length; 191 | yl = y.length; 192 | for (i = 0; i < yl; i++) { 193 | if (elmnt == y[i]) { 194 | arrNo.push(i); 195 | } else { 196 | y[i].classList.remove("select-arrow-active"); 197 | } 198 | } 199 | for (i = 0; i < xl; i++) { 200 | if (arrNo.indexOf(i)) { 201 | x[i].classList.add("select-hide"); 202 | } 203 | } 204 | } 205 | 206 | /* If the user clicks anywhere outside the select box, 207 | then close all select boxes: */ 208 | document.addEventListener("click", closeAllSelect); 209 | //////////////////////////////// 210 | //click zoom img 211 | // Get the modal 212 | var modal = document.getElementById("myModal"); 213 | 214 | // Get the image and insert it inside the modal - use its "alt" text as a caption 215 | var img = document.getElementById("myImg"); 216 | var modalImg = document.getElementById("img01"); 217 | var captionText = document.getElementById("caption"); 218 | 219 | if (img) { 220 | img.addEventListener("click", addSrc, false); 221 | } 222 | function addSrc() { 223 | modal.style.display = "block"; 224 | modalImg.src = this.src; 225 | captionText.innerHTML = this.alt; 226 | } 227 | 228 | // Get the element that closes the modal 229 | var span = document.getElementsByClassName("close")[0]; 230 | 231 | // When the user clicks on (x), close the modal 232 | span.onclick = function () { 233 | modal.style.display = "none"; 234 | }; 235 | //filter 236 | var dayFrom, dayTo, state; 237 | var yourSelect = document.getElementById("filterByState"); 238 | function getFilter() { 239 | dayFrom = new Date(document.getElementById("start").value); 240 | dayTo = document.getElementById("end").value; 241 | state = yourSelect.options[yourSelect.selectedIndex].value; 242 | 243 | console.log(dayFrom); 244 | console.log(dayTo); 245 | console.log(typeof dayFrom); 246 | console.log(state); 247 | filterByState(state); 248 | } 249 | //filter by state 250 | 251 | function filterByState(state) { 252 | filter = state.toUpperCase(); 253 | ul = document.getElementById("myUL"); 254 | li = ul.getElementsByTagName("li"); 255 | for (i = 0; i < li.length; i++) { 256 | a = li[i].getElementsByTagName("a")[0]; 257 | response = a.textContent || a.innerText; 258 | if (response.toUpperCase().indexOf(filter) > -1) { 259 | li[i].style.display = ""; 260 | } else { 261 | li[i].style.display = "none"; 262 | } 263 | } 264 | } 265 | //filter by date 266 | 267 | function filterByDate(dayFrom, dayTo) { 268 | filter = state.toUpperCase(); 269 | ul = document.getElementById("myUL"); 270 | li = ul.getElementsByTagName("li"); 271 | for (i = 0; i < li.length; i++) { 272 | a = li[i].getElementsByTagName("a")[0]; 273 | response = a.textContent || a.innerText; 274 | if (response.toUpperCase().indexOf(filter) > -1) { 275 | li[i].style.display = ""; 276 | } else { 277 | li[i].style.display = "none"; 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /app/js/call.js: -------------------------------------------------------------------------------- 1 | firebase.initializeApp(config); 2 | const ref = firebase.storage().ref(); 3 | const db = firebase.firestore(); 4 | var database = firebase.database().ref(); 5 | var yourVideo = document.getElementById("yourVideo"); 6 | var friendsVideo = document.getElementById("friendsVideo"); 7 | var yourId = Math.floor(Math.random() * 1000000000); 8 | var ranListenId = makeId(30); 9 | var state = "Call"; 10 | var snap; 11 | var file; 12 | var image; 13 | var users = []; 14 | var idUser = ""; 15 | var servers = { 16 | iceServers: [{ urls: "stun:stun.l.google.com:19302" }], 17 | }; 18 | var pc = new RTCPeerConnection(servers); 19 | var count = 0; 20 | pc.onicecandidate = (event) => 21 | event.candidate 22 | ? sendMessage(yourId, JSON.stringify({ ice: event.candidate })) 23 | : console.log("Sent All Ice"); 24 | pc.onaddstream = (event) => (friendsVideo.srcObject = event.stream); 25 | 26 | document.querySelector("#ShowButton").innerHTML = "Gọi"; 27 | 28 | const listenChange = db 29 | .collection("requests") 30 | .where("id", "==", ranListenId) 31 | .onSnapshot((querySnapshot) => { 32 | querySnapshot.docChanges().forEach((snapshot) => { 33 | snap = snapshot; 34 | if (snapshot.doc.data().request == true) { 35 | state = "Connecting"; 36 | document.querySelector("#ShowButton").innerHTML = "Hủy Cuộc Gọi"; 37 | } else if (snapshot.doc.data().completed == false) { 38 | state = "Disconnect"; 39 | document.querySelector("#ShowButton").innerHTML = "Dừng Cuộc Gọi"; 40 | } else { 41 | state = "Call"; 42 | window.location.reload(); 43 | } 44 | }); 45 | }); 46 | 47 | const getUser = db 48 | .collection("users") 49 | .where("id", "!=", "6mQ06EQZhpay2JFSS2N9N9LCvBj1") 50 | .get() 51 | .then((snapshot) => { 52 | snapshot.docs.forEach((doc) => { 53 | let items = doc.data(); 54 | 55 | users.push(items); 56 | }); 57 | showUser(); 58 | }); 59 | 60 | function showUser() { 61 | users.forEach((user) => { 62 | var ul = document.querySelector("ul"); 63 | var li = document.createElement("li"); 64 | var img = document.createElement("img"); 65 | var h5 = document.createElement("h5"); 66 | var p = document.createElement("p"); 67 | var a1 = document.createElement("a"); 68 | var a2 = document.createElement("a"); 69 | var i = document.createElement("i"); 70 | var id = document.createElement("p"); 71 | 72 | li.className = "collection-item avatar"; 73 | li.id = "userItem"; 74 | img.className = "circle"; 75 | h5.className = "title"; 76 | a2.className = "secondary-content"; 77 | a2.id = "btn-call"; 78 | i.className = "material-icons li-user"; 79 | 80 | img.src = 81 | user.urlToImage == "" 82 | ? "https://avatars2.githubusercontent.com/u/60530946?s=460&u=e342f079ed3571122e21b42eedd0ae251a9d91ce&v=4" 83 | : user.urlToImage; 84 | //console.log(img.src); 85 | h5.textContent = user.username; 86 | p.textContent = "Phòng : "; 87 | a1.textContent = user.dept; 88 | i.textContent = "phone"; 89 | a2.href = "#"; 90 | i.id = "call-video"; 91 | 92 | i.onclick = () => (idUser = user.id); 93 | 94 | a2.appendChild(i); 95 | p.appendChild(a1); 96 | li.appendChild(img); 97 | li.appendChild(h5); 98 | li.appendChild(p); 99 | li.appendChild(a2); 100 | li.appendChild(id); 101 | 102 | ul.appendChild(li); 103 | }); 104 | } 105 | 106 | function sendMessage(senderId, data) { 107 | var msg = database.push({ sender: senderId, message: data }); 108 | count++; 109 | msg.remove(); 110 | if (count == 1) { 111 | addDataToFirestore(data); 112 | } 113 | } 114 | 115 | async function addDataToFirestore(data) { 116 | const name = +new Date() + "-" + file.name; 117 | const task = ref.child("Photos").child(name).put(file); 118 | task 119 | .then((snapshot) => snapshot.ref.getDownloadURL()) 120 | .then(async (url) => { 121 | const res = await db.collection("requests").add({ 122 | idSend: "6mQ06EQZhpay2JFSS2N9N9LCvBj1", 123 | receiveID: idUser, 124 | completed: false, 125 | request: true, 126 | publishAt: firebase.firestore.FieldValue.serverTimestamp(), 127 | responcedTime: firebase.firestore.FieldValue.serverTimestamp(), 128 | urlToImage: url, 129 | filePath: "", 130 | responce: "", 131 | sdp: data, 132 | id: ranListenId, 133 | }); 134 | }) 135 | .catch(console.error); 136 | } 137 | function readMessage(data) { 138 | var msg = JSON.parse(data.val().message); 139 | var sender = data.val().sender; 140 | if (sender != yourId) { 141 | if (msg.ice != undefined) { 142 | pc.addIceCandidate(new RTCIceCandidate(msg.ice)); 143 | } else if (msg.sdp.type == "offer") { 144 | var r = confirm("Answer call?"); 145 | if (r == true) { 146 | pc.setRemoteDescription(new RTCSessionDescription(msg.sdp)) 147 | .then(() => pc.createAnswer()) 148 | .then((answer) => pc.setLocalDescription(answer)) 149 | .then(() => 150 | sendMessage(yourId, JSON.stringify({ sdp: pc.localDescription })) 151 | ); 152 | } else { 153 | alert("Rejected the call"); 154 | } 155 | } else if (msg.sdp.type == "answer") { 156 | msg.sdp.sdp = msg.sdp.sdp.replace( 157 | "useinbandfec=1", 158 | "useinbandfec=1; stereo=1; maxaveragebitrate=2000" 159 | ); 160 | pc.setRemoteDescription(new RTCSessionDescription(msg.sdp)); 161 | } 162 | } 163 | } 164 | database.on("child_added", readMessage); 165 | function showMyFace() { 166 | navigator.mediaDevices 167 | .getUserMedia({ audio: true, video: true }) 168 | .then((stream) => (yourVideo.srcObject = stream)) 169 | .then((stream) => pc.addStream(stream)); 170 | } 171 | function showFriendsFace() { 172 | console.log(idUser); 173 | if (idUser != "") { 174 | if (state == "Call") { 175 | if (file != null || file == "") { 176 | pc.createOffer() 177 | .then((offer) => { 178 | console.log(offer.sdp); 179 | offer.sdp = offer.sdp.replace( 180 | "useinbandfec=1", 181 | "useinbandfec=1; stereo=1; maxaveragebitrate=2000" 182 | ); 183 | pc.setLocalDescription(offer); 184 | }) 185 | .then(() => { 186 | sendMessage(yourId, JSON.stringify({ sdp: pc.localDescription })); 187 | }); 188 | } else { 189 | alert("Chọn hình!"); 190 | } 191 | } else if (state == "Connecting") { 192 | snap.doc.ref.update({ 193 | request: false, 194 | completed: true, 195 | responce: "Missing", 196 | }); 197 | } else if (state == "Disconnect") { 198 | snap.doc.ref.update({ 199 | request: false, 200 | completed: true, 201 | responce: "Not Responding", 202 | }); 203 | } else { 204 | } 205 | } else { 206 | alert("Chọn người nhận cuộc gọi!"); 207 | } 208 | } 209 | 210 | function makeId(length) { 211 | var result = ""; 212 | var characters = 213 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 214 | var charactersLength = characters.length; 215 | for (var i = 0; i < length; i++) { 216 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 217 | } 218 | return result; 219 | } 220 | 221 | function capture() { 222 | var canvas = document.getElementById("canvas"); 223 | var video = document.getElementById("yourVideo"); 224 | canvas.width = video.videoWidth; 225 | canvas.height = video.videoHeight; 226 | canvas 227 | .getContext("2d") 228 | .drawImage(video, 0, 0, video.videoWidth, video.videoHeight); // for drawing the video element on the canvas 229 | 230 | if (canvas.getContext) { 231 | var mySrc = canvas.toDataURL("image/png"); 232 | file = dataURLtoFile(mySrc, "filename.png"); 233 | } 234 | } 235 | 236 | function processBase64Image(dataString) { 237 | var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/), 238 | response = {}; 239 | 240 | if (matches.length !== 3) { 241 | return new Error("Invalid input string"); 242 | } 243 | 244 | response.type = matches[1]; 245 | response.data = new Buffer(matches[2], "base64"); 246 | 247 | return response; 248 | } 249 | 250 | function offCamera() { 251 | navigator.mediaDevices 252 | .getUserMedia({ audio: true, video: true }) 253 | .then((stream) => (yourVideo.srcObject = stream)) 254 | .then((stream) => { 255 | stream.getAudioTracks()[0].stop() 256 | stream.getVideoTracks()[0].stop(); 257 | stream.stop(); 258 | }); 259 | } 260 | 261 | document.getElementById("capture").addEventListener("click", capture); 262 | 263 | function dataURLtoFile(dataurl, filename) { 264 | var arr = dataurl.split(","), 265 | mime = arr[0].match(/:(.*?);/)[1], 266 | bstr = atob(arr[1]), 267 | n = bstr.length, 268 | u8arr = new Uint8Array(n); 269 | while (n--) { 270 | u8arr[n] = bstr.charCodeAt(n); 271 | } 272 | return new File([u8arr], filename, { type: mime }); 273 | } 274 | 275 | function readURL(input) { 276 | if (input.files && input.files[0]) { 277 | var reader = new FileReader(); 278 | var image = new Image(); 279 | 280 | reader.onload = function (e) { 281 | image.src = e.target.result; 282 | console.log(image.src); 283 | 284 | var canvas = document.getElementById("canvas"); 285 | var context = canvas.getContext("2d"); 286 | context.clearRect(0, 0, canvas.width, canvas.height); 287 | canvas 288 | .getContext("2d") 289 | .drawImage(image, 0, 0, canvas.width, canvas.height); 290 | 291 | image.onload = function () { 292 | context.drawImage(image, 0, 0, canvas.width, canvas.height); 293 | }; 294 | }; 295 | 296 | reader.readAsDataURL(input.files[0]); 297 | 298 | file = input.files[0]; 299 | console.log(file); 300 | } 301 | } 302 | 303 | console.log(idUser); 304 | --------------------------------------------------------------------------------