Code Monkey home page Code Monkey logo

discors's Introduction

DISCORS

Live Site

image 2-12-19 at 10 43 am

Overview

Discors is a clone of the popular chat app Discord. Users are able to create and join servers, send and accept/reject friend requests, upload custom avatars and server icons, create text channels and voice channels within servers, and message each other through text channels or through direct messaging.

The majority of Discors was designed and built within a span of 10 days.

Technologies

Discors was built using React/Redux for the frontend and Ruby on Rails and PostgreSQL for the backend. ActionCables were used to implement the live chat features and notifications. WebRTC was used in conjunction with ActionCables to create voice channels. User avatars and server icons are uploaded to AWS S3 through Active Storage.

Features

Users

  • Users can edit change their username and email
  • Users can upload a custom avatar
  • Users can login from multiple devices at the same time

image 2-13-19 at 7 37 pm

Servers

  • Users can create a new server (the user is then the admin of that server)
  • Users can upload a custom server icon when creating a server
  • Servers are created with default "general" voice and text channels
  • Users can join an existing server
  • Users can unsubscribe from a server
  • Users can delete a server if they are the admin
  • Users can see all the other members in a server

image 2-13-19 at 5 46 pm

Text Channels

  • Users can create text channels in servers they are members of
  • Only the server admin can delete channels
  • Server members can chat with each other inside of text channels. All new messages will be sent to all users of that text channel in real time
  • URLs in messages display as links
  • YouTube and image links show previews and are playable in chat

image 2-14-19 at 10 11 pm

Voice Channels

  • Users can talk to other members inside of voice channels
  • If you are behind a symetric NAT or certain firewalls WebRTC may not be able to create a peer to peer connection with your computer. The solution to this would be to implement a TURN relay server, however, these are expensive to operate. A TURN server provides a fallback solution for clients that cannot establish a Peer to Peer connection and acts as a media server proxy between the two peers.

Friends & Friend Requests

  • Users can send a friend request by clicking on a users avatar of username
  • Friend requests are sent in real time
  • Users can cancel outgoing friend requests
  • Users can accept or ignore incoming friend requests
  • Users can remove friends from their friends list
  • Users can see who is online/offline

image 2-13-19 at 5 56 pm

Direct Messaging

  • Users can send direct messages by clicking on a users avatar of username
  • Users can remove direct messages without losing their message history
  • Users receive direct message notifications

Bot

  • When a new user is created a bot will send them a direct message
  • Users can instruct the bot to send a friend request or a delayed message (to test notifications)
  • The bot can provide useful information about the site

image 2-13-19 at 5 59 pm

Challenges

WebRTC

Working with WebRTC proved to be very challenging. The core of WebRTC is RTCPeerConnection. In order to establish peer to peer media exchange, RTCPeerConnection provides an API to complete a handshake between two browsers. During this handshake, the browsers share the information necessary to set up their peer-to-peer connection: session descriptions (browser capabilities) and ICE candidates (publicly accessible IP and port information).

The steps are:

  1. Browser requests access to User A's media stream.
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
  this.localstream = stream;
}).then(() => this.handleJoinSession());
  1. User A enters the room.
handleJoinSession() {
  const that = this;
  this.voiceSession = App.cable.subscriptions.create(
    { channel: "VoiceChannel", channelId: this.props.channel.id },
    {
      connected: () => {
        this.voiceSession.broadcastData({
          type: JOIN_ROOM,
          from: this.props.currentUserId
        });
      },
      received: data => {
        console.log("received", data);
        if (data.from === this.props.currentUserId) return;
        switch (data.type) {
          case JOIN_ROOM:
            return this.joinRoom(data);
          case EXCHANGE:
            if (data.to !== this.props.currentUserId) return;
            return this.exchange(data);
          case REMOVE_USER:
            return this.removeUser(data);
          default:
            return;
        }
      },
      broadcastData: function (data) { return this.perform("broadcast", { channelId: that.props.channel.id, data }) }
    });
}
  1. Create a peer connection for User A, then create an offer that will contain their session description (SDP). Set offer as User Aโ€™s local description. Then send the offer to User B using Action Cable.
createPC(userId, isOffer) {
const ice = { iceServers: [{ urls: "stun:stun1.l.google.com:19302" }, { urls: "stun:stun2.l.google.com:19302" }] };
let pc = new RTCPeerConnection(ice);
this.pcPeers[userId] = pc;
this.localstream.getTracks().forEach(track => pc.addTrack(track, this.localstream));
if (isOffer) {
  pc.createOffer().then(offer => {
    pc.setLocalDescription(offer).then(() => {
      this.voiceSession.broadcastData({
        type: EXCHANGE,
        from: this.props.currentUserId,
        to: userId,
        sdp: JSON.stringify(pc.localDescription)
      });
    });
  }).catch(this.logError);
}
  1. User B receives the offer and sets it as their remote description. User B then generates an answer to the offer and sets the answer as their local description. Then User B sends their answer to User A using Action Cable.
if (data.sdp) {
  const sdp = JSON.parse(data.sdp);
  pc.setRemoteDescription(sdp).then(() => {
    if (sdp.type === "offer") {
      pc.createAnswer().then(answer => {
        pc.setLocalDescription(answer).then(() => {
          this.voiceSession.broadcastData({
            type: EXCHANGE,
            from: this.props.currentUserId,
            to: data.from,
            sdp: JSON.stringify(pc.localDescription)
          });
        });
      });
    }
  }).catch(this.logError);
}
  1. User A and B will now have local and remote descriptions. Concurrently, we need to send ICE candidates via Action Cable as they become available so that User A and B can connect
if (data.candidate) {
  pc.addIceCandidate(new RTCIceCandidate(JSON.parse(data.candidate)))
    .then(() => console.log("Ice candidate added"))
    .catch(this.logError);
}
  1. When enough candidates have been exchanged for a direct connection to be possible, the peer connection is established
pc.onicecandidate = event => {
  if (event.candidate) {
    this.voiceSession.broadcastData({
      type: EXCHANGE,
      from: this.props.currentUserId,
      to: userId,
      candidate: JSON.stringify(event.candidate)
    });
  }
};

pc.ontrack = event => {
  const element = document.createElement("audio");
  element.id = `remoteAudioContainer+${userId}`;
  element.autoplay = "autoplay";
  element.srcObject = event.streams[0];
  this.remoteAudioContainer.current.appendChild(element);
};

SQL

I also wanted to decrease my initial load times as much as possible, so I worte the following custom query, decreasing my total database queries by 9.

@users = User.distinct.select('users.*').left_outer_joins(:friend_requests)
  .left_outer_joins(:incoming_friend_requests).left_outer_joins(:friendships)
  .where("incoming_friend_requests_users.user_id = :current_user_id OR friend_requests.friend_id = :current_user_id OR friendships.friend_id = :current_user_id OR users.id IN (:dm_user_ids)", current_user_id: current_user_id, dm_user_ids: dm_user_ids)
  .includes(:sessions, :server_memberships)

Which in SQL would be:

SELECT DISTINCT 
  users.* 
FROM 
  users
LEFT OUTER JOIN 
  friend_requests 
ON 
  friend_requests.user_id = users.id
LEFT OUTER JOIN 
  friend_requests AS incoming_friend_requests_users 
ON 
  incoming_friend_requests_users.friend_id = users.id
LEFT OUTER JOIN 
  friendships 
ON 
  friendships.user_id = users.id
WHERE 
  incoming_friend_requests_users.user_id = 1040
  OR friend_requests.friend_id = 1040 
  OR friendships.friend_id = 1040 
  OR users.id IN (1040,1054,1041,1052,1051,1053,1046);

Please see the wiki for more information.

discors's People

Contributors

jeffdeliso avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

discors's Issues

Issues #1

###Frontend State

  • Our slice of state for currentUser should just be an id pointing to the users slice of state.

###Backend Routes

  • GET request for server_memberships is unnecessary, we should just pass that information down on login. Same goes for servers and friendships, assuming that we can't view servers when not logged in. I'd say that you probably do want to make a separate request for messages though.

Issues #3

Great job so far! Some of the things that I'm going to list aren't issues per say, but suggestions that I'll leave up to you if you want to change/implement.

  • We don't want any dead links in our apps. The Download for macOS button should either be changed to something else or removed.

  • If the settings cog in the bottom left isn't going to have a separate menu pop-up, you may want it to be more obviously a logout button.

  • There should be an ability to leave servers that you are not an admin of.

  • In your MVP's you list that only an admin of a channel should be able to create text channels. However, i t may be good to keep the functionality it as is for demoing purposes. Feel free to amend your MVP's if you decide against it.

  • There is currently no ability to delete text channels (as far as I can tell). If anyone can create a text channel, at the very least the admin should be able to delete them.

  • Currently no ability to accept friend requests.

Auth Issues

Note that all of these issues are based on the version live on heroku as of 1/23 4:30pm

  • It looks like fonts are not being imported correctly.

  • Image assets are not loading in. Lots of 404 errors.

  • The "Need an account? Register" portion is missing space between "?" and "Register"

  • Slight vertical spacing issues within the form, but that may also be affected by the change in font.

How to setup?

Hello! I was just wondering how I would get this up and running. What I mean by that is what commands would I use to properly set this up.

Thanks in advance!

Documentation

would love it , if you add some kind of instructions !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.