In the previous article, monkeyHi has shared the basic use of Agora signaling SDk, the scenarios that can be implemented, and analyzed the interface principle of server-side Demo. This article, the author will take you to carry on the simple practice, the realization of a simple web-based chat room prototype.

This post was first published in the RTC developer community and can be shared with the author by clicking here.

At the same time, we welcome those interested in real-time audio and video technology to participate in 👉 Audio website Agora SDK application experience contest

Based on Agora’s already published Web Demo, it has the most basic real-time chat room functionality with a simple configuration.

Obtain the Web Demo

We modified a chat room based on the official Demo. Download the Web Demo and decompress it to the following directory:

| - Agora_Signaling_Web | - libs / / SDK here | -- - | samples / / demo here Agora - Signaling - Tutorial - Web/demo/chat roomsCopy the code

Open agora-Signaling – tutorial-Web in the Samples directory with Visual Studio Code

| - Agora - Signaling - Tutorial - Web | - SRC | ├ ─ assets | │ ├ ─ images | │ └ ─ stylesheets | ├ ─ pages | │ ├ ─ index | │ └ ─ the meeting | └ ─ utils | - static | -- agora. Config. Js / / configuration AppId here | -- AgoraSig. Js / / copy it to assets directoryCopy the code

Install dependencies and run Demo

A quick word about the SDK on the Web side. The SDK is so wrapped that you don’t even need to know websockt or WebrTC. Just call the corresponding functional interface.

Next, we run the demo together, which is a Webpack project that professional Web development engineers will not be unfamiliar with.

  • First, copy agorAsig.js to the asstes directory.
  • Second, NPM install
  • Next, configure appID. Open SRC \static\agora.config.js and change AGORA_APP_ID to our own appID
  • Finally, NPM start

At this point, the browser should have a page pop up (see figure below).

Enter a user name casually, we can enter the chat room, of course, you can add your own user authentication business.

We opened two tabs, joined the same P2P channel as accontA and accontB, and tried to send messages to each other.

The author found that our chat room does not need to run the server side. The server introduced in front, from the interface, its function is more inclined to do system broadcast, system message notification. Therefore, if you want to implement chat software with cloud message backup function, you must implement storage and upload backup on the end.

It is no exaggeration to say that this chat room, as long as a page service can run.

Web Demo code description

When we get a Web project, the first thing we look at is package.json

We can see what packages the project depends on and the startup script for the project.

  "scripts": {
    "test": "jest ./test"/ / test"lint": "eslint .", // eslint formats the current directory"format": "eslint . --fix", // eslint fixes the code format in the current directory"dev": "cross-env NODE_ENV=development webpack-dev-server --open", // This will start webpack-dev-server and open the page in a browser"start": "npm run dev"// Same function as above"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"/ / compile},Copy the code

Configuration file static/agora.config.js

You only need to configure it in the following two lines of code.

const AGORA_APP_ID = '6cb4f3s4c67404d4ba0ec0b'  // appid
const AGORA_CERTIFICATE_ID = ' '// If the token mode is enabled, configure Certificate_idCopy the code

The SDK file static/AgoraSig. Js

This is the same SDK file as in the lib directory and can be replaced with a new SDK.

For newly downloaded demo, you should also copy this file to the SRC/Assets directory.

Utils directory

Several utility classes are encapsulated in the utils directory.

signalingClient.js

In this file, the signaling SDK is further encapsulated, converting some actions to promises and replacing Callback with events.

/**
 * Wrapper for Agora Signaling SDK
 * Transfer some action to Promise and use Event instead of Callback
 */
import EventEmitter from 'events'; // Signaling client classexport default class SignalingClient {
  constructor(appId, appcertificate) {
    this._appId = appId;
    this._appcert = appcertificate;
    // Init signal using signal sdk
    this.signal = Signal(appId) // eslint-disable-line 
    // init event emitter for channel/session/call
    this.channelEmitter = new EventEmitter();
    this.sessionEmitter = new EventEmitter();
  }

  /**
   * @description login agora signaling server and init 'session'* @description use sessionEmitter to resolve session's callback * @param {String} account * @param {*} token default to be omitted * @returns {Promise} */ login(account, token = '_no_need_token') { this.account = account; return new Promise((resolve, reject) => { this.session = this.signal.login(account, token); // Proxy callback on session to sessionEmitter [ 'onLoginSuccess', 'onError', 'onLoginFailed', 'onLogout', 'onMessageInstantReceive', 'onInviteReceived' ].map(event => { return (this.session[event] = (... args) => { this.sessionEmitter.emit(event, ... args); }); }); // Promise.then this.sessionEmitter.once('onLoginSuccess', uid => { this._uid = uid; resolve(uid); }); // Promise.catch this.sessionEmitter.once('onLoginFailed', (... args) => { reject(... args); }); }); } /** * @description logout agora Signaling Server * exit signaling service * @returns {Promise} */ logout() {return new Promise((resolve, reject) => { this.session.logout(); this.sessionEmitter.once('onLogout', (... args) => { resolve(... args); }); }); } /** * @description join channel * @description use channelEmitter to resolve channel's callback
   * @param {String} channel
   * @returns {Promise}
   */
  join(channel) {
    this._channel = channel;
    return new Promise((resolve, reject) => {
      if(! this.session) { throw { Message:'"session" must be initialized before joining channel'}; } this.channel = this.session.channelJoin(channel); // Proxy callback on channel to channelEmitter ['onChannelJoined'.'onChannelJoinFailed'.'onChannelLeaved'.'onChannelUserJoined'.'onChannelUserLeaved'.'onChannelUserList'.'onChannelAttrUpdated'.'onMessageChannelReceive'
      ].map(event => {
        return(this.channel[event] = (... args) => { this.channelEmitter.emit(event, ... args); }); }); // Promise.then this.channelEmitter.once('onChannelJoined', (... args) => { resolve(... args); }); // Promise.catch this.channelEmitter.once('onChannelJoinFailed', (... args) => { this.channelEmitter.removeAllListeners() reject(... args); }); }); } /** * @description leave channel * @returns {Promise} */leave() {
    return new Promise((resolve, reject) => {
      if (this.channel) {
        this.channel.channelLeave();
        this.channelEmitter.once('onChannelLeaved', (... args) => { this.channelEmitter.removeAllListeners() resolve(... args); }); }else{ resolve(); }}); } /** * @description send p2p message * send p2p message * @descriptionif you want to send an object, use JSON.stringify
   * @param {String} peerAccount
   * @param {String} text
   */
  sendMessage(peerAccount, text) {
    this.session && this.session.messageInstantSend(peerAccount, text);
  }

  /**
   * @description broadcast message inThe channel * Sends channel messages * @descriptionifyou want to send an object, Use json.stringify * To send broadcastMessage * @param {String} text */ broadcastMessage(text) {this.channel && this.channel.messageChannelSend(text); }}Copy the code

pages

This is the implementation of two pages, the default Index page and chat page. You can modify these two pages with some knowledge of WebPack.

pages/index.js

Notice here, click Join-meeting, we get the DOM value with the ID account-name, and put the account value in the URL.

$('#join-meeting').click(function(e) {
  // Join btn clicked
  e.preventDefault();
  var account = $('#account-name').val() || ' ';
  if(checkAccount(account)) { // Account has to be a non empty numeric value window.location.href = `meeting.html? account=${account}`;
  } else{$('#account-name')
      .removeClass('is-invalid')
      .addClass('is-invalid'); }});Copy the code

The account value of the subsequent chat page is passed by the account parameter in the URL.

pages/meeting.js

Metting. Js mainly defines the client class and chat related class methods.

First, let’s look at the end of the file:

Obtains appid / / inspection and test for null const appid = AGORA_APP_ID | |' ',
  appcert = AGORA_CERTIFICATE_ID || ' ';
if(! appid) { alert('App ID missing! '); } // Get the account value from the URL, as defined by the Browser module in util/index.js.let localAccount = Browser.getParameterByName('account');
let signal = new SignalingClient(appid, appcert);
// Let channelName = Math.random() * 10000 + ""; // By default call BTN is disabled // Signaling signal.login(localAccount).then(() => {
  // Once logged in.enable the call btn
  let client = new Client(signal, localAccount);
  $('#localAccount').html(localAccount);
});
Copy the code

Next, we should focus on the Client class, which I have only excerpted here.

I believe many friends have found that the demo chat avatar is the same, silly silly can not distinguish.

In fact, in the method of generating the render message, you can customize the avatar, the default is written as a fixed image, you can actually assemble the avatar link according to acCONt, of course, you have to make your own user avatar interface.

buildMsg(msg, me, ts) {
    let html = ' ';
    let timeStr = this.compareByLastMoment(ts);
    if (timeStr) {
      html += `<div>${timeStr}</div>`;
    }
    let className = me ? 'message right clearfix' : 'message clearfix';
    html += '<li class="' + className + '" >'; // Notice the HTML += here'';
    html +=
      '<div class="bubble">' +
      Utils.safe_tags_replace(msg) +
      '<div class="corner"></div>';
    html += '<span>' + this.parseTwitterDate(ts) + '</span></div></li>';

    return html;
  }

Copy the code

You have to pay attention to cross-domain problems here. You need to proxy the interface URL to solve the cross-domain security problem. In the development state, you can directly configure the Proxy of devServer.

And some friends said, I want to keep the message record, in fact, on the end to save the message record is relatively easy.

Look for the onReceiveMessage ()

onReceiveMessage(account, msg, type) {
    let client = this;
    var conversations = this.chats.filter(function(item) {
      return item.account === account;
    });

    if (conversations.length === 0) {
      // No conversation yet, create one
      conversations = [{ id: new Date().getTime(), account: account, type: type}]; client.chats.splice(0, 0, conversations[0]); client.updateLocalStorage(); client.updateChatList(); } // You can see that the message is briefly processed below and then dropped into MSGSfor (let i = 0; i < conversations.length; i++) {
      let conversation = conversations[i];

      let msgs = this.messages[conversation.id] || [];
      let msg_item = { ts: new Date(), text: msg, account: account };
      msgs.push(msg_item);
      this.updateMessageMap(conversation, msgs);
      let chatMsgContainer = $('.chat-messages');
      if(String(conversation.id) === String(this.current_conversation.id)) { this.showMessage(this.current_conversation.id) chatMsgContainer.scrollTop(chatMsgContainer[0].scrollHeight); }}}Copy the code

Let’s see where it’s referenced.

Obviously, the onReceiveMessage() method is called for both P2P and Chanel messages. Therefore, you can modify onReceiveMessage to implement your own chat record function. Specifically through the interface storage to our own server, or with localStorage, can better achieve the web side chat record function.

Such as window. The localStorage. SetItem (‘ msglog ‘MSGS)

Since you can save it in localStorage for now, you can export chat data to JSON and CSV without much trouble.

Problems you might encounter

  1. NPM install error

    Solution: change the warehouse address; Using yarn install; Using VPN

  2. A message can be sent, but cannot be received

    Check the asset directory and static directory for agorAsig.js. If not, copy it from the SDK’s lib directory and rename it agorAsig.js

conclusion

Generally speaking, it is very simple to implement chatroom based on Agora signaling. Based on Demo, we can extend some user management services to achieve it. You can focus on optimizing the interactive experience, beautifying the UI, and focusing on the end-to-end business. However, if you want more control, you want to implement something like chat logs on the server side. To do this function based on the current version of signaling, you need to develop it yourself. The advantage of signaling is that it facilitates the realization of some message notification scenarios. And docking is very easy, as long as simple encapsulation can be directly embedded on the end. In addition, for the realization of bullet screen, you can try to send messages at the end and push messages to the interface that saves messages.