WebRTC - Bản trình diễn văn bản

Trong chương này, chúng ta sẽ xây dựng một ứng dụng khách cho phép hai người dùng trên các thiết bị riêng biệt gửi tin nhắn cho nhau bằng cách sử dụng WebRTC. Đơn của chúng tôi sẽ có hai trang. Một để đăng nhập và một để gửi tin nhắn cho người dùng khác.

Hai trang sẽ là thẻ div . Hầu hết đầu vào được thực hiện thông qua các trình xử lý sự kiện đơn giản.

Máy chủ báo hiệu

Để tạo kết nối WebRTC, khách hàng phải có khả năng truyền thông điệp mà không cần sử dụng kết nối ngang hàng WebRTC. Đây là nơi chúng tôi sẽ sử dụng HTML5 WebSockets - kết nối ổ cắm hai chiều giữa hai điểm cuối - máy chủ web và trình duyệt web. Bây giờ chúng ta hãy bắt đầu sử dụng thư viện WebSocket. Tạo tệp server.js và chèn mã sau:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

Dòng đầu tiên yêu cầu thư viện WebSocket mà chúng tôi đã cài đặt. Sau đó, chúng tôi tạo một máy chủ socket trên cổng 9090. Tiếp theo, chúng tôi lắng nghe sự kiện kết nối . Mã này sẽ được thực thi khi người dùng thực hiện kết nối WebSocket với máy chủ. Sau đó, chúng tôi lắng nghe bất kỳ tin nhắn nào do người dùng gửi. Cuối cùng, chúng tôi gửi phản hồi đến người dùng được kết nối nói rằng “Xin chào từ máy chủ”.

Trong máy chủ báo hiệu của chúng tôi, chúng tôi sẽ sử dụng tên người dùng dựa trên chuỗi cho mỗi kết nối để chúng tôi biết nơi gửi tin nhắn. Hãy thay đổi trình xử lý kết nối của chúng ta một chút -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON");
      data = {}; 
   } 
});

Bằng cách này, chúng tôi chỉ chấp nhận các tin nhắn JSON. Tiếp theo, chúng ta cần lưu trữ tất cả người dùng được kết nối ở đâu đó. Chúng tôi sẽ sử dụng một đối tượng Javascript đơn giản cho nó. Thay đổi đầu tệp của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Chúng tôi sẽ thêm một trường loại cho mọi thông báo đến từ máy khách. Ví dụ: nếu người dùng muốn đăng nhập, anh ta sẽ gửi thông báo loại đăng nhập . Hãy định nghĩa nó -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break;
   } 
});

Nếu người dùng gửi tin nhắn với kiểu đăng nhập , chúng tôi -

  • Kiểm tra xem có ai đã đăng nhập bằng tên người dùng này chưa.
  • Nếu vậy, hãy nói với người dùng rằng anh ta chưa đăng nhập thành công.
  • Nếu không có ai đang sử dụng tên người dùng này, chúng tôi thêm tên người dùng làm khóa cho đối tượng kết nối.
  • Nếu một lệnh không được nhận dạng, chúng tôi sẽ gửi một lỗi.

Đoạn mã sau là một chức năng trợ giúp để gửi tin nhắn đến một kết nối. Thêm nó vào tệp server.js -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Khi người dùng ngắt kết nối, chúng ta nên xóa kết nối của nó. Chúng tôi có thể xóa người dùng khi sự kiện đóng được kích hoạt. Thêm mã sau vào trình xử lý kết nối -

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Sau khi đăng nhập thành công người dùng muốn gọi một người khác. Anh ta nên đưa ra đề nghị cho người dùng khác để đạt được nó. Thêm trình xử lý phiếu mua hàng -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
   //setting that UserA connected with UserB 
   connection.otherName = data.name; 
	
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
     
   break;

Đầu tiên, chúng tôi nhận được kết nối của người dùng mà chúng tôi đang cố gắng gọi. Nếu nó tồn tại, chúng tôi gửi cho anh ta chi tiết đề nghị . Chúng tôi cũng thêm otherName vào đối tượng kết nối . Điều này được thực hiện để đơn giản hóa việc tìm kiếm nó sau này.

Trả lời câu trả lời có một mẫu tương tự mà chúng tôi đã sử dụng trong trình xử lý phiếu mua hàng . Máy chủ của chúng tôi chỉ chuyển qua tất cả các tin nhắn dưới dạng câu trả lời cho người dùng khác. Thêm mã sau vào sau trình xử lý phiếu mua hàng -

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name;
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

Phần cuối cùng là xử lý ứng cử viên ICE giữa những người dùng. Chúng tôi sử dụng cùng một kỹ thuật chỉ chuyển thông điệp giữa những người dùng. Sự khác biệt chính là các thông báo ứng viên có thể xảy ra nhiều lần cho mỗi người dùng theo bất kỳ thứ tự nào. Thêm trình xử lý ứng viên -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Để cho phép người dùng của chúng tôi ngắt kết nối với người dùng khác, chúng tôi nên triển khai chức năng treo máy. Nó cũng sẽ yêu cầu máy chủ xóa tất cả các tham chiếu của người dùng. Thêm trình xử lý rời -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null;
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   }
	
   break;

Thao tác này cũng sẽ gửi cho người dùng khác sự kiện nghỉ việc để anh ta có thể ngắt kết nối ngang hàng của mình tương ứng. Chúng ta cũng nên xử lý trường hợp người dùng làm rớt kết nối của mình khỏi máy chủ báo hiệu. Hãy sửa đổi trình xử lý gần của chúng ta -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;  
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

Sau đây là toàn bộ mã của máy chủ báo hiệu của chúng tôi -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	 
   //when server gets a message from a connected user 
   connection.on('message', function(message) {
	 
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		  
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged", data.name); 
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            }
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name);
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave"
               });
            }  
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break;
				
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
         } 
      } 
   });
	
   connection.send("Hello world");
	
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Ứng dụng Khách hàng

Một cách để kiểm tra ứng dụng này là mở hai tab trình duyệt và cố gắng gửi tin nhắn cho nhau.

Trước hết, chúng ta cần cài đặt thư viện bootstrap . Bootstrap là một khung công tác giao diện người dùng để phát triển các ứng dụng web. Bạn có thể tìm hiểu thêm tạihttp://getbootstrap.com/.Tạo một thư mục có tên, chẳng hạn như “textchat”. Đây sẽ là thư mục ứng dụng gốc của chúng tôi. Bên trong thư mục này, hãy tạo một tệp package.json (nó cần thiết để quản lý các phụ thuộc npm) và thêm các thông tin sau:

{ 
   "name": "webrtc-textochat", 
   "version": "0.1.0", 
   "description": "webrtc-textchat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Sau đó chạy npm cài đặt bootstrap . Thao tác này sẽ cài đặt thư viện bootstrap trong thư mục textchat / node_modules .

Bây giờ chúng ta cần tạo một trang HTML cơ bản. Tạo tệp index.html trong thư mục gốc với mã sau:

<html> 

   <head> 
      <title>WebRTC Text Demo</title> 
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/> 
   </head>
 
   <style> 
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
   </style>
	
   <body> 
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4"> 
               <h2>WebRTC Text Demo. Please sign in</h2> 
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup" placeholder = "Login" 
                  required = "" autofocus = "">
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
                  Sign in</button> 
            </div> 
         </div> 
			
      </div>
		
      <div id = "callPage" class = "call-page container">
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4 text-center"> 
               <div class = "panel panel-primary"> 
                  <div class = "panel-heading">Text chat</div> 
                  <div id = "chatarea" class = "panel-body text-left"></div> 
               </div> 
            </div> 
         </div>
			
         <div class = "row text-center form-group"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" type = "text" 
                  placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div> 
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "msgInput" type = "text" placeholder = "message" /> 
               <button id = "sendMsgBtn" class = "btn-success btn">Send</button> 
            </div> 
         </div>
			
      </div> 
		
      <script src = "client.js"></script> 
		
   </body>
 
</html>

Trang này sẽ quen thuộc với bạn. Chúng tôi đã thêm tệp bootstrap css. Chúng tôi cũng đã xác định hai trang. Cuối cùng, chúng tôi đã tạo một số trường văn bản và các nút để lấy thông tin từ người dùng. Trên trang “trò chuyện”, bạn sẽ thấy thẻ div có ID “chatarea”, nơi tất cả các tin nhắn của chúng tôi sẽ được hiển thị. Lưu ý rằng chúng tôi đã thêm một liên kết vào tệp client.js .

Bây giờ chúng ta cần thiết lập kết nối với máy chủ báo hiệu của mình. Tạo tệp client.js trong thư mục gốc với mã sau:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
}; 
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate":
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Bây giờ chạy máy chủ báo hiệu của chúng tôi thông qua máy chủ nút . Sau đó, bên trong thư mục gốc, hãy chạy lệnh tĩnh và mở trang bên trong trình duyệt. Bạn sẽ thấy đầu ra bảng điều khiển sau:

Bước tiếp theo là thực hiện đăng nhập người dùng với tên người dùng duy nhất. Chúng tôi chỉ cần gửi một tên người dùng đến máy chủ, sau đó cho chúng tôi biết liệu nó có được lấy hay không. Thêm mã sau vào tệp client.js của bạn -

//****** 
//UI selectors block
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput'); 
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
   } 
	
};

Đầu tiên, chúng tôi chọn một số tham chiếu đến các phần tử trên trang. Chúng tôi ẩn trang cuộc gọi. Sau đó, chúng tôi thêm một trình nghe sự kiện trên nút đăng nhập. Khi người dùng nhấp vào nó, chúng tôi gửi tên người dùng của anh ta đến máy chủ. Cuối cùng, chúng tôi triển khai lệnh gọi lại handleLogin. Nếu đăng nhập thành công, chúng tôi sẽ hiển thị trang cuộc gọi, thiết lập kết nối ngang hàng và tạo kênh dữ liệu.

Để bắt đầu kết nối ngang hàng với một kênh dữ liệu, chúng ta cần -

  • Tạo đối tượng RTCPeerConnection
  • Tạo kênh dữ liệu bên trong đối tượng RTCPeerConnection của chúng tôi

Thêm mã sau vào “Khối bộ chọn giao diện người dùng” -

var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var chatArea = document.querySelector('#chatarea'); 

var yourConn; 
var dataChannel;

Sửa đổi hàm handleLogin -

function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //**********************
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      };
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      };
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      };
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };  
   } 
};

Nếu đăng nhập thành công, ứng dụng sẽ tạo đối tượng RTCPeerConnection và thiết lập trình xử lý onicecandidate để gửi tất cả các icecandidate đã tìm thấy đến đồng đẳng khác. Nó cũng tạo ra một DataChannel. Lưu ý rằng khi tạo đối tượng RTCPeerConnection, đối số thứ hai trong phương thức khởi tạo tùy chọn: [{RtpDataChannels: true}] là bắt buộc nếu bạn đang sử dụng Chrome hoặc Opera. Bước tiếp theo là tạo một phiếu mua hàng cho người ngang hàng khác. Khi người dùng nhận được đề nghị, anh ta sẽ tạo câu trả lời và bắt đầu giao dịch các ứng viên ICE. Thêm mã sau vào tệp client.js -

//initiating a call
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
		
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Chúng tôi thêm một trình xử lý nhấp chuột vào nút Gọi, nút này sẽ bắt đầu một phiếu mua hàng. Sau đó, chúng tôi triển khai một số trình xử lý mà trình xử lý onmessage mong đợi . Chúng sẽ được xử lý không đồng bộ cho đến khi cả hai người dùng đã kết nối.

Bước tiếp theo là triển khai tính năng gác máy. Thao tác này sẽ ngừng truyền dữ liệu và yêu cầu người dùng khác đóng kênh dữ liệu. Thêm mã sau -

//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 
 
function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};

Khi người dùng nhấp vào nút Treo lên -

  • Nó sẽ gửi một thông báo "để lại" cho người dùng khác.
  • Nó sẽ đóng RTCPeerConnection và cũng như kênh dữ liệu.

Bước cuối cùng là gửi tin nhắn đến một người ngang hàng khác. Thêm trình xử lý “nhấp chuột” vào nút “gửi tin nhắn” -

//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Bây giờ hãy chạy mã. Bạn sẽ có thể đăng nhập vào máy chủ bằng hai tab trình duyệt. Sau đó, bạn có thể thiết lập kết nối ngang hàng với người dùng khác và gửi tin nhắn cho họ cũng như đóng kênh dữ liệu bằng cách nhấp vào nút "Treo máy".

Sau đây là toàn bộ tệp client.js -

//our username 
var name; 
var connectedUser; 

//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090'); 

conn.onopen = function () { 
   console.log("Connected to the signaling server");
};
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data); 
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
}; 

conn.onerror = function (err) { 
   console.log("Got error", err); 
}; 

//alias for sending JSON encoded messages 
function send(message) { 

   //attach the other peer username to our messages
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
 
//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 

var chatArea = document.querySelector('#chatarea'); 
var yourConn; 
var dataChannel; 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) {
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]}); 
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      }; 
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      }; 
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };
		
   } 
};
 
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
	
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
      send({ 
         type: "answer", 
         answer: answer 
      }); 
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
 
//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 

function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};
 
//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});