├── log └── .keep ├── tmp └── .keep ├── vendor └── .keep ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ └── rooms.rake ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── user_test.rb │ └── room_test.rb ├── system │ ├── .keep │ └── rooms_test.rb ├── controllers │ ├── .keep │ ├── pages_controller_test.rb │ ├── rooms_controller_test.rb │ └── sessions_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── users.yml │ └── rooms.yml ├── integration │ ├── .keep │ ├── rooms_for_guests_test.rb │ └── rooms_for_members_test.rb ├── jobs │ └── xirsys_credentials_job_test.rb ├── application_system_test_case.rb ├── lib │ └── room_pruning_test.rb └── test_helper.rb ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── bg.png │ │ ├── favicon │ │ │ ├── favicon.ico │ │ │ ├── mstile-70x70.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-310x310.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── browserconfig.xml.erb │ │ │ ├── manifest.json.erb │ │ │ └── safari-pinned-tab.svg │ │ └── logo.svg │ ├── javascripts │ │ ├── channels │ │ │ └── .keep │ │ ├── cable.js │ │ └── application.js │ ├── config │ │ └── manifest.js │ └── stylesheets │ │ ├── alerts.scss │ │ ├── buttons.scss │ │ ├── video.scss │ │ ├── custom.scss │ │ └── application.scss ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── guest_user.rb │ ├── user.rb │ └── room.rb ├── controllers │ ├── concerns │ │ ├── .keep │ │ └── current_user_concern.rb │ ├── users │ │ └── registrations_controller.rb │ ├── pages_controller.rb │ ├── api │ │ └── v1 │ │ │ └── api_controller.rb │ ├── sessions_controller.rb │ ├── hello_world_controller.rb │ ├── application_controller.rb │ └── rooms_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── hello_world.html.erb │ │ ├── mailer.html.erb │ │ └── application.html.erb │ ├── rooms │ │ ├── show.json.jbuilder │ │ ├── _room.html.erb │ │ ├── _auth_form.html.erb │ │ ├── index.html.erb │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ ├── authenticate.html.erb │ │ ├── _form.html.erb │ │ ├── show.html.erb │ │ └── js │ │ │ └── webrtc.js │ ├── hello_world │ │ └── index.html.erb │ ├── pages │ │ ├── _form.html.erb │ │ └── home.html.erb │ ├── shared │ │ ├── _flash_alerts.html.erb │ │ └── _navbar.html.erb │ ├── application │ │ └── _favicon.html.erb │ └── devise │ │ ├── unlocks │ │ └── new.html.erb │ │ ├── passwords │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ ├── shared │ │ └── _links.html.erb │ │ ├── sessions │ │ └── new.html.erb │ │ └── registrations │ │ ├── new.html.erb │ │ └── edit.html.erb ├── helpers │ ├── pages_helper.rb │ ├── rooms_helper.rb │ ├── sessions_helper.rb │ └── application_helper.rb ├── jobs │ ├── application_job.rb │ └── xirsys_credentials_job.rb ├── channels │ ├── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ ├── ice_channel.rb │ └── session_channel.rb ├── mailers │ └── application_mailer.rb ├── javascript │ ├── bundles │ │ ├── HelloWorld │ │ │ ├── constants │ │ │ │ └── helloWorldConstants.js │ │ │ ├── actions │ │ │ │ └── helloWorldActionCreators.js │ │ │ ├── store │ │ │ │ └── helloWorldStore.js │ │ │ ├── reducers │ │ │ │ └── helloWorldReducer.js │ │ │ ├── startup │ │ │ │ └── HelloWorldApp.jsx │ │ │ ├── containers │ │ │ │ └── HelloWorldContainer.js │ │ │ └── components │ │ │ │ └── HelloWorld.jsx │ │ └── src │ │ │ └── components │ │ │ └── HomeForm.jsx │ └── packs │ │ ├── hello-world-bundle.js │ │ └── application.js ├── services │ └── fetch_ice_servers.rb └── presenters │ └── room_presenter.rb ├── .postcssrc.yml ├── config ├── webpack │ ├── environment.js │ ├── test.js │ ├── production.js │ └── development.js ├── spring.rb ├── boot.rb ├── environment.rb ├── initializers │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── assets.rb │ ├── inflections.rb │ ├── react_on_rails.rb │ ├── devise_token_auth.rb │ ├── friendly_id.rb │ └── devise.rb ├── cable.yml ├── routes.rb ├── locales │ ├── en.yml │ └── devise.en.yml ├── application.rb ├── secrets.yml ├── webpacker.yml ├── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── favicon.json ├── puma.rb └── database.yml ├── bin ├── bundle ├── rake ├── rails ├── yarn ├── webpack ├── webpack-dev-server ├── spring ├── update └── setup ├── config.ru ├── db ├── migrate │ ├── 20171003185008_add_index_to_room.rb │ ├── 20171006053920_add_password_to_rooms.rb │ ├── 20171003230334_add_references_to_room.rb │ ├── 20171003183539_add_slug_to_rooms.rb │ ├── 20171003043014_create_rooms.rb │ ├── 20171003215543_devise_token_auth_create_users.rb │ ├── 20171003183349_create_friendly_id_slugs.rb │ └── 20171003215524_devise_create_users.rb ├── seeds.rb └── schema.rb ├── Procfile.dev ├── Rakefile ├── Procfile.dev-server ├── .babelrc ├── package.json ├── .gitignore ├── Guardfile ├── Gemfile ├── README.md └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/helpers/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module PagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/rooms_helper.rb: -------------------------------------------------------------------------------- 1 | module RoomsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/views/rooms/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.name @room.name 2 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /.postcssrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | postcss-smart-import: {} 3 | postcss-cssnext: {} 4 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/views/rooms/_room.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to room.name, room %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/bg.png -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | const environment = require('./environment') 2 | 3 | module.exports = environment.toWebpackConfig() 4 | -------------------------------------------------------------------------------- /app/models/guest_user.rb: -------------------------------------------------------------------------------- 1 | # Guest users for unauthenticated accounts 2 | class GuestUser < User 3 | attr_accessor :email 4 | end 5 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | const environment = require('./environment') 2 | 3 | module.exports = environment.toWebpackConfig() 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | const environment = require('./environment') 2 | 3 | module.exports = environment.toWebpackConfig() 4 | -------------------------------------------------------------------------------- /test/jobs/xirsys_credentials_job_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class XirsysCredentialsJobTest < ActiveJob::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/favicon.ico -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class Users::RegistrationsController < Devise::RegistrationsController 3 | end 4 | -------------------------------------------------------------------------------- /app/assets/images/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /app/views/hello_world/index.html.erb: -------------------------------------------------------------------------------- 1 |

    Hello World

    2 | <%= react_component("HelloWorldApp", props: @hello_world_props, prerender: false) %> 3 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /app/assets/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /app/assets/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /app/assets/images/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /app/assets/images/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /app/assets/images/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /app/assets/images/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /test/controllers/pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class PagesControllerTest < ActionDispatch::IntegrationTest 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/rooms_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class RoomsControllerTest < ActionDispatch::IntegrationTest 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/images/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class SessionsControllerTest < ActionDispatch::IntegrationTest 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/images/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /app/assets/images/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeanpaulsio/rails-video-chat-app/HEAD/app/assets/images/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /app/assets/stylesheets/alerts.scss: -------------------------------------------------------------------------------- 1 | @import "custom"; 2 | 3 | .alert-notice, .alert-alert { 4 | background: $state-success-bg; 5 | color: $state-success-text; 6 | } 7 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'admin@webrtc-on-rails.herokuapp.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /db/migrate/20171003185008_add_index_to_room.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToRoom < ActiveRecord::Migration[5.1] 2 | def change 3 | add_index :rooms, :name, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171006053920_add_password_to_rooms.rb: -------------------------------------------------------------------------------- 1 | class AddPasswordToRooms < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :rooms, :password, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/constants/helloWorldConstants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | export const HELLO_WORLD_NAME_UPDATE = 'HELLO_WORLD_NAME_UPDATE'; 4 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: rails s -p 3000 2 | 3 | # Next line runs a watch process with webpack 4 | client: sh -c 'rm -rf public/packs/* || true && bundle exec rake react_on_rails:locale && bin/webpack -w' 5 | -------------------------------------------------------------------------------- /db/migrate/20171003230334_add_references_to_room.rb: -------------------------------------------------------------------------------- 1 | class AddReferencesToRoom < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :rooms, :user, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def home 3 | redirect_to rooms_path unless current_user.is_a? GuestUser 4 | @room = Room.new 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/buttons.scss: -------------------------------------------------------------------------------- 1 | @import "custom"; 2 | 3 | .btn-salmon { 4 | background: $brand-primary; 5 | color: white; 6 | &:hover { 7 | background: darken($brand-primary, 5%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/controllers/api/v1/api_controller.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class Api::V1::ApiController < ApplicationController 3 | include DeviseTokenAuth::Concerns::SetUserByToken 4 | 5 | before_action :authenticate_api_v1_user! 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def create 3 | head :no_content 4 | ActionCable.server.broadcast "session_#{params[:roomName]}", params 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171003183539_add_slug_to_rooms.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToRooms < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :rooms, :slug, :string 4 | add_index :rooms, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/channels/ice_channel.rb: -------------------------------------------------------------------------------- 1 | class IceChannel < ApplicationCable::Channel 2 | def subscribed 3 | stream_from "ice_#{params[:id]}" 4 | end 5 | 6 | def unsubscribed 7 | # Any cleanup needed when channel is unsubscribed 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/hello_world_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HelloWorldController < ApplicationController 4 | layout 'hello_world' 5 | 6 | def index 7 | @hello_world_props = { name: 'Stranger' } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /app/channels/session_channel.rb: -------------------------------------------------------------------------------- 1 | class SessionChannel < ApplicationCable::Channel 2 | def subscribed 3 | stream_from "session_#{params[:id]}" 4 | end 5 | 6 | def unsubscribed 7 | # Any cleanup needed when channel is unsubscribed 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://redistogo:b9b18e3300ef1878746a63a0b4b3bdc2@tarpon.redistogo.com:11990/ 10 | channel_prefix: video-chat-app_production 11 | -------------------------------------------------------------------------------- /db/migrate/20171003043014_create_rooms.rb: -------------------------------------------------------------------------------- 1 | class CreateRooms < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :rooms do |t| 4 | t.string :name 5 | t.integer :status, default: 0 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Procfile.dev-server: -------------------------------------------------------------------------------- 1 | web: rails s -p 3000 2 | 3 | # Next line runs the webpack-dev-server 4 | # You can edit config/webpacker.yml to set HMR to true to see hot reloading 5 | client: sh -c 'rm -rf public/packs/* || true && bundle exec rake react_on_rails:locale && bin/webpack-dev-server' 6 | -------------------------------------------------------------------------------- /app/jobs/xirsys_credentials_job.rb: -------------------------------------------------------------------------------- 1 | require 'rest-client' 2 | require 'json' 3 | 4 | # API Calls to xirsys.com 5 | class XirsysCredentialsJob < ApplicationJob 6 | queue_as :default 7 | 8 | def perform(room_id) 9 | FetchIceServers.new(room_id).run 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module ApplicationHelper 3 | def present(object, klass = nil) 4 | klass ||= "#{object.class}Presenter".constantize 5 | presenter = klass.new(object, self) 6 | yield presenter if block_given? 7 | presenter 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/javascript/packs/hello-world-bundle.js: -------------------------------------------------------------------------------- 1 | import ReactOnRails from 'react-on-rails'; 2 | import HelloWorldApp from '../bundles/HelloWorld/startup/HelloWorldApp'; 3 | 4 | // This is how react_on_rails can see the HelloWorld in the browser. 5 | ReactOnRails.register({ 6 | HelloWorldApp 7 | }); 8 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; 4 | 5 | export const updateName = (text) => ({ 6 | type: HELLO_WORLD_NAME_UPDATE, 7 | text, 8 | }); 9 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/store/helloWorldStore.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import helloWorldReducer from '../reducers/helloWorldReducer'; 3 | 4 | const configureStore = (railsProps) => ( 5 | createStore(helloWorldReducer, railsProps) 6 | ); 7 | 8 | export default configureStore; 9 | -------------------------------------------------------------------------------- /app/views/layouts/hello_world.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReactOnRailsWithWebpacker 5 | <%= csrf_meta_tags %> 6 | <%= javascript_pack_tag 'hello-world-bundle' %> 7 | 8 | 9 | 10 | <%= yield %> 11 | 12 | 13 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | jerry: 2 | uid: jerry@test.com 3 | email: jerry@test.com 4 | encrypted_password: <%= User.new.send(:password_digest, 'foobar') %> 5 | 6 | kramer: 7 | uid: kramer@test.com 8 | email: kramer@test.com 9 | encrypted_password: <%= User.new.send(:password_digest, 'foobar') %> 10 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/pages/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @room do |f| %> 2 |
    3 |

    Create a Room

    4 | <%= f.text_field :name, required: true, class: "form-control bg-light" %> 5 |
    6 | <%= f.submit "Create New Room", class: "btn btn-salmon btn-block" %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/video.scss: -------------------------------------------------------------------------------- 1 | #localViewContainer { 2 | width: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | overflow: hidden; 7 | } 8 | 9 | #remoteViewContainer { 10 | width: 100%; 11 | video { 12 | width: 300px; 13 | height: auto; 14 | border: 3px solid #ccc; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/assets/images/favicon/browserconfig.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/rooms.yml: -------------------------------------------------------------------------------- 1 | temporary_room: 2 | name: Temporary Room 3 | status: temporary 4 | 5 | unrestricted_room: 6 | name: Unrestricted Room 7 | status: unrestricted 8 | user: jerry 9 | 10 | restricted_room: 11 | name: Restricted Room 12 | status: restricted 13 | password: <%= BCrypt::Password.create('foobar') %> 14 | user: kramer 15 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 2 | // layout file, like app/views/layouts/application.html.erb 3 | 4 | import ReactOnRails from "react-on-rails"; 5 | import HomeForm from "../bundles/src/components/HomeForm"; 6 | 7 | ReactOnRails.register({ 8 | HomeForm 9 | }); 10 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/concerns/current_user_concern.rb: -------------------------------------------------------------------------------- 1 | module CurrentUserConcern 2 | extend ActiveSupport::Concern 3 | 4 | def current_user 5 | super || guest_user 6 | end 7 | 8 | def guest_user 9 | guest = GuestUser.new 10 | salt = BCrypt::Engine.generate_salt 11 | guest.email = "guest+#{salt}@example.com" 12 | guest 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/rooms/_auth_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_tag authenticate_room_path do %> 2 |
    3 |

    Password

    4 | <%= password_field_tag :password, nil, class: "form-control" %> 5 |
    6 | 7 |
    8 | <%= submit_tag "Enter Room", class: "btn btn-salmon btn-block" %> 9 |
    10 | <% end %> 11 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /app/views/pages/home.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | <%= image_tag("logo.svg",size: "50", alt: "WebRTC on Rails", class: "img-fluid m-2") %> 5 |

    WebRTC on Rails

    6 | <%= react_component "HomeForm" %> 7 |
    8 |
    9 |
    10 | -------------------------------------------------------------------------------- /app/views/rooms/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Your Rooms

    5 | 6 |
      7 | <%= render @rooms %> 8 |
    9 | 10 | <%= link_to "Create a Room", new_room_path, class: "btn btn-salmon" %> 11 |
    12 |
    13 |
    14 | 15 | 16 | -------------------------------------------------------------------------------- /app/views/rooms/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Room Settings

    5 |
    6 |
    7 | <%= render 'form' %> 8 |
    9 |
    10 |
    11 |
    12 |
    13 | -------------------------------------------------------------------------------- /app/views/rooms/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Create a Room

    5 |
    6 |
    7 | <%= render 'form' %> 8 |
    9 |
    10 |
    11 |
    12 |
    13 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/views/rooms/authenticate.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Enter Room Password

    5 |
    6 |
    7 | <%= render 'auth_form' %> 8 |
    9 |
    10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'webpack' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("webpacker", "webpack") 18 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; 3 | 4 | const name = (state = '', action) => { 5 | switch (action.type) { 6 | case HELLO_WORLD_NAME_UPDATE: 7 | return action.text; 8 | default: 9 | return state; 10 | } 11 | }; 12 | 13 | const helloWorldReducer = combineReducers({ name }); 14 | 15 | export default helloWorldReducer; 16 | -------------------------------------------------------------------------------- /app/views/shared/_flash_alerts.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% flash.each do |message_type, message| %> 3 |
    4 | 7 |
    8 | <%= message %> 9 |
    10 |
    11 | <% end %> 12 |
    13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": "> 1%", 9 | "uglify": true 10 | }, 11 | "useBuiltIns": true 12 | } 13 | ], 14 | "react" 15 | ], 16 | "plugins": [ 17 | "syntax-dynamic-import", 18 | "transform-object-rest-spread", 19 | [ 20 | "transform-class-properties", 21 | { 22 | "spec": true 23 | } 24 | ] 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'webpack-dev-server' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("webpacker", "webpack-dev-server") 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "video-chat-app", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/webpacker": "^3.0.2", 6 | "axios": "^0.16.2", 7 | "babel-preset-react": "^6.24.1", 8 | "coffeescript": "1.12.7", 9 | "prop-types": "^15.6.0", 10 | "react": "^16.0.0", 11 | "react-dom": "^16.0.0", 12 | "react-on-rails": "^9.0.3", 13 | "react-redux": "^5.0.6", 14 | "redux": "^3.7.2" 15 | }, 16 | "devDependencies": { 17 | "webpack-dev-server": "^2.9.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/services/fetch_ice_servers.rb: -------------------------------------------------------------------------------- 1 | require 'rest-client' 2 | require 'json' 3 | 4 | # Get ICE STUN and TURN list 5 | class FetchIceServers 6 | def initialize(room_id) 7 | @room_id = room_id 8 | end 9 | 10 | def run 11 | broadcast_list(ice_server_list) 12 | end 13 | 14 | def ice_server_list 15 | response = RestClient.put ENV['GET_XIRSYS_ICE'], accept: :json 16 | response 17 | end 18 | 19 | def broadcast_list(response) 20 | ActionCable.server.broadcast "ice_#{@room_id}", response 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/custom.scss: -------------------------------------------------------------------------------- 1 | // Redefine Bootstrap variables here 2 | @import url('https://fonts.googleapis.com/css?family=Work+Sans:300,400,700'); 3 | 4 | // Fonts 5 | $font-family-base: 'Work Sans', sans-serif; 6 | 7 | // Colors 8 | $brand-primary: #fa4932; 9 | 10 | // Links 11 | $link-color: $brand-primary !default; 12 | $link-hover-color: darken($link-color, 15%) !default; 13 | $link-hover-decoration: underline !default; 14 | 15 | // Alerts 16 | $state-success-text: #ffffff; 17 | $state-success-bg: #fa4932; 18 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/assets/images/favicon/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebRTC on Rails", 3 | "icons": [ 4 | { 5 | "src": "<%= asset_path 'favicon/android-chrome-192x192.png' %>", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "<%= asset_path 'favicon/android-chrome-512x512.png' %>", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "custom"; 2 | @import "bootstrap"; 3 | @import "buttons"; 4 | @import "alerts"; 5 | @import "video"; 6 | 7 | // Global 8 | * { 9 | transition: width 0.4s, height 0.4s, font-size 0.4s, font-weight 0.4s; 10 | } 11 | 12 | html, 13 | body { 14 | width: 100%; 15 | height: 100%; 16 | background-image: image-url("bg.png"); 17 | } 18 | 19 | h1 { 20 | color: $brand-primary; 21 | font-weight: 700; 22 | } 23 | 24 | h2 { 25 | font-size: 2em; 26 | font-weight: 300; 27 | } 28 | 29 | #leave-btn-container { 30 | display: none; 31 | } 32 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class User < ApplicationRecord 3 | has_many :rooms, dependent: :destroy 4 | 5 | devise :database_authenticatable, :registerable, 6 | :recoverable, :rememberable, :trackable, :validatable 7 | include DeviseTokenAuth::Concerns::User 8 | 9 | before_validation :set_provider 10 | before_validation :set_uid 11 | 12 | private 13 | 14 | def set_provider 15 | self[:provider] = 'email' if self[:provider].blank? 16 | end 17 | 18 | def set_uid 19 | self[:uid] = self[:email] if self[:uid].blank? && self[:email].present? 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class ApplicationController < ActionController::Base 3 | protect_from_forgery with: :null_session, 4 | only: proc { |c| c.request.format.json? } 5 | skip_before_action :verify_authenticity_token 6 | 7 | before_action :store_user_location!, if: :storable_location? 8 | include CurrentUserConcern 9 | 10 | def storable_location? 11 | request.get? && is_navigational_format? && !devise_controller? && !request.xhr? 12 | end 13 | 14 | def store_user_location! 15 | store_location_for(:user, request.fullpath) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/startup/HelloWorldApp.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | 4 | import configureStore from '../store/helloWorldStore'; 5 | import HelloWorldContainer from '../containers/HelloWorldContainer'; 6 | 7 | // See documentation for https://github.com/reactjs/react-redux. 8 | // This is how you get props from the Rails view into the redux store. 9 | // This code here binds your smart component to the redux store. 10 | const HelloWorldApp = (props) => ( 11 | 12 | 13 | 14 | ); 15 | 16 | export default HelloWorldApp; 17 | -------------------------------------------------------------------------------- /app/views/rooms/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @room do |f| %> 2 | <% unless @room.name %> 3 |
    4 |

    Room Name

    5 | <%= f.text_field :name, class: "form-control", required: true %> 6 |
    7 | <% end %> 8 | 9 |
    10 |

    Room Password optional

    11 | <%= f.text_field :password, class: "form-control" %> 12 |
    13 | 14 | <%= f.hidden_field :user_id, value: current_user.id %> 15 | 16 |
    17 | <%= f.submit 'Save', class: "btn btn-salmon btn-block" %> 18 |
    19 | <% end %> 20 | -------------------------------------------------------------------------------- /test/lib/room_pruning_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class RoomPruningTest < ActiveSupport::TestCase 5 | test 'tests rake task' do 6 | VideoChatApp::Application.load_tasks 7 | Rake::Task['rooms:destroy_temporary_all'].invoke 8 | assert_equal Room.temporary.count, 0 9 | 10 | Rake::Task['rooms:create_temporary_old'].invoke 11 | assert_equal Room.temporary.count, 10 12 | 13 | Rake::Task['rooms:create_temporary_new'].invoke 14 | assert_equal Room.temporary.count, 15 15 | 16 | Rake::Task['rooms:destroy_temporary_old'].invoke 17 | assert_equal Room.temporary.count, 5 18 | 19 | Rake::Task.clear 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebRTC on Rails 5 | 6 | <%= csrf_meta_tags %> 7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 8 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_pack_tag 'application' %> 10 | <%= render 'application/favicon' %> 11 | 12 | 13 | 14 | <%= render 'shared/navbar' %> 15 | <%= render 'shared/flash_alerts' %> 16 | <%= yield %> 17 | <%= yield :page_js %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.js: -------------------------------------------------------------------------------- 1 | // Simple example of a React "smart" component 2 | 3 | import { connect } from 'react-redux'; 4 | import HelloWorld from '../components/HelloWorld'; 5 | import * as actions from '../actions/helloWorldActionCreators'; 6 | 7 | // Which part of the Redux global state does our component want to receive as props? 8 | const mapStateToProps = (state) => ({ name: state.name }); 9 | 10 | // Don't forget to actually use connect! 11 | // Note that we don't export HelloWorld, but the redux "connected" version of it. 12 | // See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples 13 | export default connect(mapStateToProps, actions)(HelloWorld); 14 | -------------------------------------------------------------------------------- /app/views/application/_favicon.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | /node_modules 17 | /yarn-error.log 18 | 19 | .byebug_history 20 | 21 | # Ignore application configuration 22 | /config/application.yml 23 | 24 | # Ignore Spring files. 25 | /spring/*.pid 26 | /public/packs 27 | /public/packs-test 28 | /node_modules 29 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /db/migrate/20171003215543_devise_token_auth_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.1] 2 | def up 3 | add_column :users, :provider, :string, null: false, default: 'email' 4 | add_column :users, :uid, :string, null: false, default: '' 5 | add_column :users, :tokens, :text 6 | 7 | # inspired by https://github.com/lynndylanhurley/devise_token_auth/issues/181 8 | User.reset_column_information 9 | User.find_each do |user| 10 | user.uid = user.email 11 | user.provider = 'email' 12 | user.save! 13 | end 14 | 15 | add_index :users, [:uid, :provider], unique: true 16 | end 17 | 18 | def down 19 | remove_columns :users, :provider, :uid, :tokens 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20171003183349_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 } 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery3 14 | //= require popper 15 | //= require bootstrap-sprockets 16 | //= require jquery_ujs 17 | //= require rails-ujs 18 | //= require_tree . 19 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Resend unlock instructions

    7 | 8 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 9 | <%= devise_error_messages! %> 10 | 11 |
    12 | <%= f.label :email %> 13 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 14 |
    15 | 16 |
    17 | <%= f.submit "Resend unlock instructions", class: "btn btn-salmon btn-block" %> 18 |
    19 | <% end %> 20 | 21 | <%= render "devise/shared/links" %> 22 |
    23 | 24 |
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Forgot Password?

    7 | 8 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 9 | <%= devise_error_messages! %> 10 | 11 |
    12 | <%= f.label :email %> 13 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 14 |
    15 | 16 |
    17 | <%= f.submit "Send me reset password instructions", class: "btn btn-salmon btn-block" %> 18 |
    19 | <% end %> 20 | 21 | <%= render "devise/shared/links" %> 22 |
    23 | 24 |
    25 |
    26 |
    27 | 28 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../config/environment', __FILE__) 2 | require 'rails/test_help' 3 | require 'minitest/reporters' 4 | Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new(color: true) 5 | 6 | class ActiveSupport::TestCase 7 | fixtures :all 8 | end 9 | 10 | class ActionDispatch::IntegrationTest 11 | def sign_up_as(args) 12 | get new_user_registration_path 13 | post user_registration_path, params: { 14 | user: { 15 | email: args[:email], 16 | password: 'foobar', 17 | password_confirmation: 'foobar' 18 | } 19 | } 20 | end 21 | 22 | def log_in_as(user, password: 'foobar') 23 | get new_user_session_path 24 | post user_session_path, params: { 25 | user: { 26 | email: user.email, 27 | password: password 28 | } 29 | } 30 | end 31 | 32 | def log_out 33 | delete destroy_user_session_path 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get 'hello_world', to: 'hello_world#index' 3 | # Web Routes 4 | root 'pages#home' 5 | 6 | resources :rooms do 7 | member do 8 | get :toggle_status 9 | get :claim 10 | post :authenticate 11 | end 12 | end 13 | 14 | post '/sessions', to: 'sessions#create' 15 | devise_for :users, skip: :registrations 16 | devise_scope :user do 17 | resource :registration, 18 | only: %i[new create edit update], 19 | path: 'users', 20 | path_names: { new: 'sign_up' }, 21 | controller: 'devise/registrations', 22 | as: :user_registration do 23 | get :cancel 24 | end 25 | end 26 | 27 | mount ActionCable.server, at: '/cable' 28 | 29 | # API Routes 30 | namespace :api, constraints: { format: 'json' } do 31 | namespace :v1 do 32 | mount_devise_token_auth_for 'User', at: 'auth' 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module VideoChatApp 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.1 13 | config.assets.initialize_on_precompile = false 14 | 15 | config.middleware.insert_before 0, Rack::Cors do 16 | allow do 17 | origins '*' 18 | resource '*', headers: :any, 19 | expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], 20 | methods: %i[get post options delete put] 21 | end 22 | end 23 | 24 | config.middleware.use ActionDispatch::Flash 25 | config.middleware.use ActionDispatch::Cookies 26 | config.middleware.use ActionDispatch::Session::CookieStore 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/tasks/rooms.rake: -------------------------------------------------------------------------------- 1 | namespace :rooms do 2 | desc 'Creates 10 Temporary Rooms over 24 hours old' 3 | task create_temporary_old: :environment do 4 | 10.times do |i| 5 | Room.create!(name: "temp-old-#{i}", created_at: 2.days.ago) 6 | end 7 | end 8 | 9 | desc 'Creates 5 Temporary Rooms less than 24 hours old' 10 | task create_temporary_new: :environment do 11 | 5.times do |i| 12 | Room.create!(name: "temp-new-#{i}") 13 | end 14 | end 15 | 16 | desc 'Destroys all Temporary rooms' 17 | task destroy_temporary_all: :environment do 18 | Room.temporary.destroy_all 19 | end 20 | 21 | desc 'Removes Temporary Rooms over 24 hours old' 22 | task destroy_temporary_old: :environment do 23 | @rooms = Room.temporary 24 | @time_limit_in_hours = 24 25 | 26 | @rooms.each do |room| 27 | time_created = room.created_at.to_time 28 | hours_alive = ((Time.now - time_created) / 1.hour).floor 29 | 30 | room.destroy if hours_alive > @time_limit_in_hours 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :minitest, spring: 'bin/rails test', all_on_start: false do 2 | watch(%r{^test/(.*)/?(.*)_test\.rb$}) 3 | watch('test/test_helper.rb') { 'test' } 4 | watch('config/routes.rb') { integration_tests } 5 | watch(%r{^app/models/(.*?)\.rb$}) do |matches| 6 | "test/models/#{matches[1]}_test.rb" 7 | end 8 | 9 | # Runs all integration tests when a view is changed 10 | watch(%r{app/views/*}) do 11 | integration_tests 12 | end 13 | 14 | # Runs all integration tests when a controller is changed 15 | watch(%r{app/controllers/*}) do 16 | integration_tests 17 | end 18 | 19 | # Returns the integration tests corresponding to the given resource. 20 | def integration_tests(resource = :all) 21 | if resource == :all 22 | Dir['test/integration/*'] 23 | else 24 | Dir["test/integration/#{resource}_*.rb"] 25 | end 26 | end 27 | 28 | # Returns the controller tests corresponding to the given resource. 29 | def controller_test(resource) 30 | "test/controllers/#{resource}_controller_test.rb" 31 | end 32 | 33 | # Returns all tests for the given resource. 34 | def resource_tests(resource) 35 | integration_tests(resource) << controller_test(resource) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/javascript/bundles/HelloWorld/components/HelloWorld.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | export default class HelloWorld extends React.Component { 5 | static propTypes = { 6 | name: PropTypes.string.isRequired, // this is passed from the Rails view 7 | }; 8 | 9 | /** 10 | * @param props - Comes from your rails view. 11 | */ 12 | constructor(props) { 13 | super(props); 14 | 15 | // How to set initial state in ES6 class syntax 16 | // https://facebook.github.io/react/docs/reusable-components.html#es6-classes 17 | this.state = { name: this.props.name }; 18 | } 19 | 20 | updateName = (name) => { 21 | this.setState({ name }); 22 | }; 23 | 24 | render() { 25 | return ( 26 |
    27 |

    28 | Hello {this.state.name}! 29 |

    30 |
    31 |
    32 | 35 | this.updateName(e.target.value)} 40 | /> 41 |
    42 |
    43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/integration/rooms_for_guests_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class RoomsForGuestsTest < ActionDispatch::IntegrationTest 5 | def setup 6 | get root_url 7 | 8 | assert_difference 'Room.count', 1 do 9 | post rooms_path, params: { room: { name: 'my room' } } 10 | end 11 | 12 | @room = Room.find_by_slug('my-room') 13 | end 14 | 15 | test 'cannot claim a room' do 16 | get claim_room_path(@room) 17 | @room.reload 18 | 19 | assert_nil @room.user 20 | end 21 | 22 | test 'cannot change the status of a room' do 23 | get toggle_status_room_path(@room, status: :unrestricted) 24 | assert_equal @room.status, 'temporary' 25 | 26 | get toggle_status_room_path(@room, status: :restricted) 27 | assert_equal @room.status, 'temporary' 28 | end 29 | 30 | test 'can claim a room by registering' do 31 | sign_up_as(email: 'jerry@rails.com') 32 | 33 | get claim_room_path(@room) 34 | @room.reload 35 | assert_equal @room.user.email, 'jerry@rails.com' 36 | end 37 | 38 | test 'cannot password protect a room' do 39 | patch room_path(@room), params: { room: { password: 'foobar' } } 40 | @room.reload 41 | 42 | assert_nil @room.password 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
    3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
    7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
    11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
    15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
    19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
    24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Log in

    7 | 8 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 9 | 10 | <%= devise_error_messages! %> 11 | 12 |
    13 | <%= f.label :email %> 14 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 15 |
    16 | 17 |
    18 | <%= f.label :password %> 19 | <%= f.password_field :password, autocomplete: "off", class: "form-control" %> 20 |
    21 | 22 | <% if devise_mapping.rememberable? -%> 23 |
    24 | <%= f.check_box :remember_me %> 25 | <%= f.label :remember_me %> 26 |
    27 | <% end %> 28 | 29 |
    30 | <%= f.submit "Log in", class: "btn btn-salmon btn-block" %> 31 |
    32 | 33 | <% end %> 34 | 35 | <%= render "devise/shared/links" %> 36 |
    37 | 38 |
    39 |
    40 |
    41 | -------------------------------------------------------------------------------- /app/presenters/room_presenter.rb: -------------------------------------------------------------------------------- 1 | class RoomPresenter 2 | include Rails.application.routes.url_helpers 3 | 4 | # model and view layer as args 5 | def initialize(room, template) 6 | @room = room 7 | @template = template 8 | end 9 | 10 | def h 11 | @template 12 | end 13 | 14 | def register_link(current_user) 15 | return unless @room.user.nil? && (current_user.is_a? GuestUser) 16 | 17 | wrap_list h.link_to('Register to Claim', 18 | new_user_registration_path(name: @room.name)) 19 | end 20 | 21 | def claim_room_link(current_user) 22 | return unless @room.user.nil? && !(current_user.is_a? GuestUser) 23 | 24 | wrap_list h.link_to('Claim Room', claim_room_path(@room)) 25 | end 26 | 27 | def toggle_status_link(current_user) 28 | return unless (@room.user == current_user) && (@room.restricted?) 29 | 30 | wrap_list h.link_to('Make Room Public', 31 | toggle_status_room_path(@room, status: :unrestricted)) 32 | end 33 | 34 | def room_settings_link(current_user) 35 | return unless @room.user == current_user 36 | 37 | wrap_list h.link_to('Room Settings', edit_room_path(@room)) 38 | end 39 | 40 | private 41 | 42 | def wrap_list(link_to) 43 | '
  • '.html_safe + link_to + '
  • '.html_safe 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: 220756124b87b62de8d75a15008b882ca2c1fe899efa5377ecaab3d8613a520552a8e3d6924b4220151071d7e4fd6b3b98695388305b06b48cd77b9b08f6b737 22 | 23 | test: 24 | secret_key_base: 55e305e00efbd2b78c8035d43c66dfe4ec19c385e52f5d7e9ec7356932e09c302ea174c4dc7f04045dbec01a3f9dbb02e455478d658648f264b883ea03f0806f 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /app/views/shared/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Sign up

    7 | 8 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 9 | 10 | <%= devise_error_messages! %> 11 | 12 |
    13 | <%= f.label :email %> 14 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 15 |
    16 | 17 |
    18 | <%= f.label :password %> 19 | <% if @minimum_password_length %> 20 | (<%= @minimum_password_length %> characters minimum) 21 | <% end %>
    22 | <%= f.password_field :password, autocomplete: "off", class: "form-control" %> 23 |
    24 | 25 |
    26 | <%= f.label :password_confirmation %>
    27 | <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %> 28 |
    29 | 30 |
    31 | <%= f.submit "Sign up", class: "btn btn-salmon btn-block" %> 32 |
    33 | 34 | <% end %> 35 | 36 | <%= render "devise/shared/links" %> 37 |
    38 | 39 |
    40 |
    41 |
    42 | -------------------------------------------------------------------------------- /app/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 14 | 15 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/models/room.rb: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | class Room < ApplicationRecord 3 | belongs_to :user, optional: true 4 | 5 | extend FriendlyId 6 | friendly_id :name, use: :slugged 7 | 8 | enum status: { 9 | temporary: 0, 10 | unrestricted: 1, 11 | restricted: 2 12 | } 13 | 14 | validates :name, presence: true, 15 | length: { maximum: 15 }, 16 | uniqueness: { case_sensitive: false } 17 | 18 | after_create :make_room_public 19 | before_save :create_hashed_password, if: :will_save_change_to_password? 20 | before_save :update_status, if: :will_save_change_to_password? 21 | before_save :remove_password, if: :unrestricted? 22 | 23 | def make_room_public 24 | return if user_id.nil? 25 | return unless password_is_blank? 26 | unrestricted! 27 | end 28 | 29 | def create_hashed_password 30 | self.password = password_is_blank? ? nil : BCrypt::Password.create(password) 31 | end 32 | 33 | def check_hashed_password(password_hash) 34 | BCrypt::Password.new(password_hash) 35 | end 36 | 37 | def update_status 38 | self.status = password_is_blank? ? 'unrestricted' : 'restricted' 39 | end 40 | 41 | def remove_password 42 | self.password = nil 43 | end 44 | 45 | def set_default_password 46 | self.password = BCrypt::Password.create('password') 47 | end 48 | 49 | private 50 | 51 | def password_is_blank? 52 | password.blank? || password.nil? 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Edit Account

    7 | 8 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 9 | 10 | <%= devise_error_messages! %> 11 | 12 |
    13 | <%= f.label :email %> 14 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 15 |
    16 | 17 |
    18 | <%= f.label :new_password %>
    19 | <%= f.password_field :password, autocomplete: "off", class: "form-control" %> 20 |
    21 | 22 |
    23 | <%= f.label :confirm_new_password %> 24 | <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %> 25 |
    26 | 27 |
    28 | * <%= f.label :current_password %> 29 | <%= f.password_field :current_password, autocomplete: "off", class: "form-control" %> 30 |
    31 | 32 |
    33 | <%= f.submit "Save Changes", class: "btn btn-salmon btn-block" %> 34 |
    35 | 36 | <% end %> 37 | 38 | <%= link_to "Back", :back %> 39 |
    40 | 41 |
    42 |
    43 |
    44 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_output_path: packs 7 | cache_path: tmp/cache/webpacker 8 | 9 | # Additional paths webpack should lookup modules 10 | # ['app/assets', 'engine/foo/app/assets'] 11 | resolved_paths: [] 12 | 13 | # Reload manifest.json on all requests so we reload latest compiled packs 14 | cache_manifest: false 15 | 16 | extensions: 17 | - .coffee 18 | - .erb 19 | - .js 20 | - .jsx 21 | - .ts 22 | - .vue 23 | - .sass 24 | - .scss 25 | - .css 26 | - .png 27 | - .svg 28 | - .gif 29 | - .jpeg 30 | - .jpg 31 | 32 | development: 33 | <<: *default 34 | compile: true 35 | 36 | # Reference: https://webpack.js.org/configuration/dev-server/ 37 | dev_server: 38 | https: false 39 | host: localhost 40 | port: 3035 41 | public: localhost:3035 42 | hmr: false 43 | # Inline should be set to true if using HMR 44 | inline: true 45 | overlay: true 46 | disable_host_check: true 47 | use_local_ip: false 48 | 49 | test: 50 | <<: *default 51 | compile: true 52 | 53 | # Compile test packs to a separate directory 54 | public_output_path: packs-test 55 | 56 | production: 57 | <<: *default 58 | 59 | # Production depends on precompilation of packs prior to booting for performance. 60 | compile: false 61 | 62 | # Cache manifest.json for performance 63 | cache_manifest: true 64 | -------------------------------------------------------------------------------- /test/system/rooms_test.rb: -------------------------------------------------------------------------------- 1 | require 'application_system_test_case' 2 | 3 | # :nodoc: 4 | class RoomsTest < ApplicationSystemTestCase 5 | def setup 6 | @jerrys_room = rooms(:unrestricted_room) 7 | end 8 | 9 | test 'creating a room' do 10 | skip 'UI not completed' 11 | visit root_url 12 | 13 | fill_in 'Room Name', with: 'Room 1' 14 | click_on 'Create New Room' 15 | assert_text 'Join Room' 16 | end 17 | 18 | test 'claiming a room by registering' do 19 | skip 'UI not completed' 20 | 21 | visit root_url 22 | 23 | fill_in 'Name', with: 'Room 2' 24 | click_on 'Create New Room' 25 | assert_text 'Invite by sharing this link:' 26 | 27 | click_on 'Register' 28 | fill_in 'Email', with: 'jerry@rails.com' 29 | fill_in 'Password', with: 'foobar' 30 | fill_in 'Password confirmation', with: 'foobar' 31 | click_on 'Sign up' 32 | assert_text 'Welcome! You have signed up successfully.' 33 | assert_text 'Room 2' 34 | 35 | click_on 'claim room' 36 | assert_text 'Room claimed' 37 | end 38 | 39 | test 'can enter an existing room from anywhere' do 40 | skip 'UI not completed' 41 | 42 | visit room_path(@jerrys_room) 43 | assert_text 'Join Room' 44 | end 45 | 46 | test 'friendly message when entering a non existent room' do 47 | skip 'not yet implemented' 48 | end 49 | 50 | test 'can create a room with a password' do 51 | skip 'not yet implemented' 52 | end 53 | 54 | test 'gracefully handles failed ICE requests' do 55 | skip 'not yet implemented' 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /db/migrate/20171003215524_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :users do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.inet :current_sign_in_ip 20 | t.inet :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps null: false 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |

    Change Password

    7 | 8 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 9 | <%= devise_error_messages! %> 10 | <%= f.hidden_field :reset_password_token %> 11 | 12 |
    13 | <%= f.label :password, "New password" %>
    14 | <% if @minimum_password_length %> 15 | (<%= @minimum_password_length %> characters minimum) 16 | <% end %>
    17 | <%= f.password_field :password, autofocus: true, autocomplete: "off", class: "form-control" %> 18 |
    19 | 20 |
    21 | <%= f.label :password_confirmation, "Confirm new password" %>
    22 | <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %> 23 |
    24 | 25 |
    26 | <%= f.label :email %> 27 | <%= f.email_field :email, autofocus: true, class: "form-control" %> 28 |
    29 | 30 |
    31 | <%= f.submit "Change my password", class: "btn btn-salmon btn-block" %> 32 |
    33 | <% end %> 34 | 35 | <%= render "devise/shared/links" %> 36 |
    37 | 38 |
    39 |
    40 |
    41 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

    If you are the application owner check the logs for more information.

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | # rails 9 | gem 'rails', '~> 5.1.4' 10 | 11 | gem 'bcrypt', '~> 3.1', '>= 3.1.11' 12 | gem 'bootstrap', '~> 4.0.0.beta' 13 | gem 'coffee-rails', '~> 4.2' 14 | gem 'figaro', '~> 1.1', '>= 1.1.1' 15 | gem 'friendly_id', '~> 5.2', '>= 5.2.3' 16 | gem 'jbuilder', '~> 2.5' 17 | gem 'jquery-rails', '~> 4.3', '>= 4.3.1' 18 | gem 'json', '~> 2.1' 19 | gem 'pg', '~> 0.18' 20 | gem 'puma', '~> 3.7' 21 | gem 'redis', '~> 3.0' 22 | gem 'rest-client', '~> 2.0', '>= 2.0.2' 23 | gem 'sass-rails', '~> 5.0' 24 | gem 'turbolinks', '~> 5' 25 | gem 'uglifier', '>= 1.3.0' 26 | 27 | # react on rails 28 | gem 'react_on_rails', '9.0.0' 29 | gem 'webpacker', '~> 3.0' 30 | 31 | # favicon 32 | group :development do 33 | gem 'rails_real_favicon' 34 | end 35 | 36 | # authentication 37 | gem 'devise', '~> 4.3' 38 | gem 'devise_token_auth', '~> 0.1.42' 39 | gem 'omniauth', '~> 1.7' 40 | 41 | # api 42 | gem 'rack-cors', '~> 0.4.1' 43 | 44 | group :test do 45 | gem 'guard', '~> 2.14', '>= 2.14.1' 46 | gem 'guard-minitest', '~> 2.4', '>= 2.4.6' 47 | gem 'minitest-reporters', '~> 1.1', '>= 1.1.18' 48 | gem 'rails-controller-testing', '~> 1.0', '>= 1.0.2' 49 | end 50 | 51 | group :development, :test do 52 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 53 | gem 'capybara', '~> 2.13' 54 | gem 'selenium-webdriver' 55 | end 56 | 57 | group :development do 58 | gem 'listen', '>= 3.0.5', '< 3.2' 59 | gem 'spring' 60 | gem 'spring-watcher-listen', '~> 2.0.0' 61 | gem 'web-console', '>= 3.3.0' 62 | end 63 | 64 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 65 | 66 | gem 'mini_racer', platforms: :ruby -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/favicon.json: -------------------------------------------------------------------------------- 1 | { 2 | "master_picture": "app/assets/images/logo.svg", 3 | "favicon_design": { 4 | "ios": { 5 | "picture_aspect": "background_and_margin", 6 | "background_color": "#ffffff", 7 | "margin": "35%", 8 | "assets": { 9 | "ios6_and_prior_icons": false, 10 | "ios7_and_later_icons": false, 11 | "precomposed_icons": false, 12 | "declare_only_default_icon": true 13 | } 14 | }, 15 | "desktop_browser": [ 16 | 17 | ], 18 | "windows": { 19 | "picture_aspect": "white_silhouette", 20 | "background_color": "#da532c", 21 | "on_conflict": "override", 22 | "assets": { 23 | "windows_80_ie_10_tile": false, 24 | "windows_10_ie_11_edge_tiles": { 25 | "small": false, 26 | "medium": true, 27 | "big": false, 28 | "rectangle": false 29 | } 30 | } 31 | }, 32 | "android_chrome": { 33 | "picture_aspect": "shadow", 34 | "theme_color": "#ffffff", 35 | "manifest": { 36 | "name": "WebRTC on Rails", 37 | "display": "standalone", 38 | "orientation": "not_set", 39 | "on_conflict": "override", 40 | "declared": true 41 | }, 42 | "assets": { 43 | "legacy_icon": false, 44 | "low_resolution_icons": false 45 | } 46 | }, 47 | "safari_pinned_tab": { 48 | "picture_aspect": "black_and_white", 49 | "threshold": 71.09375, 50 | "theme_color": "#ff0000" 51 | } 52 | }, 53 | "settings": { 54 | "scaling_algorithm": "Mitchell", 55 | "error_on_image_too_small": false 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/assets/images/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 16 | 20 | 24 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

    Maybe you tried to change something you didn't have access to.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /app/views/rooms/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 |
    6 | 12 | 13 | 14 | 15 |
    16 |
    17 |
    18 | 19 |
    20 |
    21 |
    22 | 25 |
    26 | 27 |
    28 | 31 |
    32 | 33 |
      34 | <% present @room do |room_presenter| %> 35 | <%= room_presenter.register_link(current_user) %> 36 | <%= room_presenter.claim_room_link(current_user) %> 37 | <%= room_presenter.toggle_status_link(current_user) %> 38 | <%= room_presenter.room_settings_link(current_user) %> 39 | <% end %> 40 |
    41 |
    42 | 43 |
    44 | 45 | 46 |
    47 | 48 |
    49 | 50 |
    51 |
    52 |
    53 | 54 |
    55 | 56 | <% content_for :page_js do %> 57 | 60 | <% end %> 61 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

    You may have mistyped the address or the page may have moved.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /test/models/room_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class RoomTest < ActiveSupport::TestCase 5 | def setup 6 | @temporary_room = rooms(:temporary_room) 7 | end 8 | 9 | test 'should not save without a name' do 10 | @temporary_room.name = '' 11 | assert_not @temporary_room.valid? 12 | end 13 | 14 | test 'should not save name longer than 15 characters' do 15 | @temporary_room.name = 'a' * 16 16 | assert_not @temporary_room.valid? 17 | end 18 | 19 | test 'should not save name that already exists' do 20 | room = Room.new(name: 'Temporary Room') 21 | assert_not room.valid? 22 | end 23 | 24 | test 'should not save status higher than 2' do 25 | assert_raises(ArgumentError) do 26 | @temporary_room.status = 3 27 | end 28 | end 29 | 30 | test 'should not save status unless valid' do 31 | assert_raises(ArgumentError) do 32 | @temporary_room.status = 'Invalid Status' 33 | end 34 | end 35 | 36 | test 'should create temporary room for guests' do 37 | room = Room.create!(name: 'Room') 38 | assert_equal room.status, 'temporary' 39 | end 40 | 41 | test 'should create unrestricted room for users' do 42 | user = users(:jerry) 43 | room = user.rooms.create!(name: 'asdfasdf') 44 | assert_equal room.status, 'unrestricted' 45 | end 46 | 47 | test 'should hash password before saving' do 48 | @temporary_room.password = 'foobar' 49 | @temporary_room.save 50 | assert_not_equal @temporary_room.password, 'foobar' 51 | end 52 | 53 | test 'should privatize a room when adding a password' do 54 | @temporary_room.password = 'foobar' 55 | @temporary_room.save 56 | assert_equal @temporary_room.status, 'restricted' 57 | end 58 | 59 | test 'should remove password when making room unrestricted' do 60 | @temporary_room.password = 'foobar' 61 | @temporary_room.save 62 | assert_equal @temporary_room.status, 'restricted' 63 | 64 | @temporary_room.unrestricted! 65 | assert_equal @temporary_room.status, 'unrestricted' 66 | assert_nil @temporary_room.password, nil 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /config/initializers/react_on_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # See docs/basics/configuration.md for many more options 4 | 5 | ReactOnRails.configure do |config| 6 | # This configures the script to run to build the production assets by webpack. Set this to nil 7 | # if you don't want react_on_rails building this file for you. 8 | config.build_production_command = "RAILS_ENV=production bin/webpack" 9 | 10 | ################################################################################ 11 | ################################################################################ 12 | # TEST CONFIGURATION OPTIONS 13 | # Below options are used with the use of this test helper: 14 | # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) 15 | ################################################################################ 16 | 17 | # If you are using this in your spec_helper.rb (or rails_helper.rb): 18 | # 19 | # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) 20 | # 21 | # with rspec then this controls what yarn command is run 22 | # to automatically refresh your webpack assets on every test run. 23 | # 24 | config.build_test_command = "RAILS_ENV=test bin/webpack" 25 | 26 | ################################################################################ 27 | ################################################################################ 28 | # SERVER RENDERING OPTIONS 29 | ################################################################################ 30 | # This is the file used for server rendering of React when using `(prerender: true)` 31 | # If you are never using server rendering, you should set this to "". 32 | # Note, there is only one server bundle, unlike JavaScript where you want to minimize the size 33 | # of the JS sent to the client. For the server rendering, React on Rails creates a pool of 34 | # JavaScript execution instances which should handle any component requested. 35 | # 36 | # While you may configure this to be the same as your client bundle file, this file is typically 37 | # different. You should have ONE server bundle which can create all of your server rendered 38 | # React components. 39 | # 40 | config.server_bundle_js_file = "hello-world-bundle.js" 41 | end 42 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | config.action_mailer.raise_delivery_errors = true 35 | config.action_mailer.delivery_method = :test 36 | host = 'localhost:3000' 37 | config.action_mailer.default_url_options = { host: host, protocol: 'http' } 38 | 39 | # Print deprecation notices to the Rails logger. 40 | config.active_support.deprecation = :log 41 | 42 | # Raise an error on page load if there are pending migrations. 43 | config.active_record.migration_error = :page_load 44 | 45 | # Debug mode disables concatenation and preprocessing of assets. 46 | # This option may cause significant delays in view rendering with a large 47 | # number of complex assets. 48 | config.assets.debug = true 49 | 50 | # Suppress logger output for asset requests. 51 | config.assets.quiet = true 52 | 53 | # Raises error for missing translations 54 | # config.action_view.raise_on_missing_translations = true 55 | 56 | # Use an evented file watcher to asynchronously detect changes in source code, 57 | # routes, locales, etc. This feature depends on the listen gem. 58 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 59 | end 60 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /config/initializers/devise_token_auth.rb: -------------------------------------------------------------------------------- 1 | DeviseTokenAuth.setup do |config| 2 | # By default the authorization headers will change after each request. The 3 | # client is responsible for keeping track of the changing tokens. Change 4 | # this to false to prevent the Authorization header from changing after 5 | # each request. 6 | # config.change_headers_on_each_request = true 7 | 8 | # By default, users will need to re-authenticate after 2 weeks. This setting 9 | # determines how long tokens will remain valid after they are issued. 10 | # config.token_lifespan = 2.weeks 11 | 12 | # Sets the max number of concurrent devices per user, which is 10 by default. 13 | # After this limit is reached, the oldest tokens will be removed. 14 | # config.max_number_of_devices = 10 15 | 16 | # Sometimes it's necessary to make several requests to the API at the same 17 | # time. In this case, each request in the batch will need to share the same 18 | # auth token. This setting determines how far apart the requests can be while 19 | # still using the same auth token. 20 | # config.batch_request_buffer_throttle = 5.seconds 21 | 22 | # This route will be the prefix for all oauth2 redirect callbacks. For 23 | # example, using the default '/omniauth', the github oauth2 provider will 24 | # redirect successful authentications to '/omniauth/github/callback' 25 | # config.omniauth_prefix = "/omniauth" 26 | 27 | # By default sending current password is not needed for the password update. 28 | # Uncomment to enforce current_password param to be checked before all 29 | # attribute updates. Set it to :password if you want it to be checked only if 30 | # password is updated. 31 | # config.check_current_password_before_update = :attributes 32 | 33 | # By default we will use callbacks for single omniauth. 34 | # It depends on fields like email, provider and uid. 35 | # config.default_callbacks = true 36 | 37 | # Makes it possible to change the headers names 38 | # config.headers_names = {:'access-token' => 'access-token', 39 | # :'client' => 'client', 40 | # :'expiry' => 'expiry', 41 | # :'uid' => 'uid', 42 | # :'token-type' => 'token-type' } 43 | 44 | # By default, only Bearer Token authentication is implemented out of the box. 45 | # If, however, you wish to integrate with legacy Devise authentication, you can 46 | # do so by enabling this flag. NOTE: This feature is highly experimental! 47 | # config.enable_standard_devise_support = false 48 | end 49 | -------------------------------------------------------------------------------- /test/integration/rooms_for_members_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # :nodoc: 4 | class RoomsForMembersTest < ActionDispatch::IntegrationTest 5 | def setup 6 | @kramers_room = rooms(:restricted_room) 7 | @jerrys_room = rooms(:unrestricted_room) 8 | @kramer = users(:kramer) 9 | @jerry = users(:jerry) 10 | 11 | log_in_as(@jerry) 12 | end 13 | 14 | test 'cannot claim another user\'s room' do 15 | get claim_room_path(@kramers_room) 16 | refute_equal @kramers_room.user, @jerry 17 | end 18 | 19 | test 'cannot toggle room status of another user\'s room' do 20 | get toggle_status_room_path(@kramers_room, status: :unrestricted) 21 | assert_equal @kramers_room.status, 'restricted' 22 | end 23 | 24 | test 'cannot set a room to private using #toggle_status' do 25 | get toggle_status_room_path(@jerrys_room, status: :restricted) 26 | assert_equal @jerrys_room.status, 'unrestricted' 27 | end 28 | 29 | test 'can create a room' do 30 | get new_room_path 31 | assert_select 'input#room_name' 32 | 33 | assert_difference 'Room.count', 1 do 34 | post rooms_path, params: { room: { name: 'jerrys room' } } 35 | end 36 | end 37 | 38 | test 'can create a room with a password' do 39 | get new_room_path 40 | assert_select 'input#room_name' 41 | assert_select 'input#room_password' 42 | 43 | assert_difference 'Room.count', 1 do 44 | post rooms_path, params: { 45 | room: { 46 | name: 'private room', 47 | password: 'foobar' 48 | } 49 | } 50 | end 51 | 52 | private_room = Room.find_by_slug('private-room') 53 | password_hash = private_room.check_hashed_password(private_room.password) 54 | 55 | assert_equal password_hash, 'foobar' 56 | assert_equal private_room.status, 'restricted' 57 | end 58 | 59 | test 'can add a password to their room' do 60 | get new_room_path 61 | assert_select 'input#room_name' 62 | 63 | assert_difference 'Room.count', 1 do 64 | post rooms_path, params: { room: { name: 'jerrys room' } } 65 | end 66 | 67 | room = Room.find_by_slug('jerrys-room') 68 | patch room_path(room), params: { room: { password: 'foobar' } } 69 | 70 | room.reload 71 | password = room.password 72 | assert_equal room.check_hashed_password(password), 'foobar' 73 | assert_equal room.status, 'restricted' 74 | end 75 | 76 | test 'can make a private room public' do 77 | log_out 78 | log_in_as(@kramer) 79 | 80 | password = @kramers_room.password 81 | assert_equal @kramers_room.status, 'restricted' 82 | assert_equal @kramers_room.check_hashed_password(password), 'foobar' 83 | 84 | get toggle_status_room_path(@kramers_room, status: :unrestricted) 85 | 86 | @kramers_room.reload 87 | assert_equal @kramers_room.status, 'unrestricted' 88 | assert_nil @kramers_room.password 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20171006053920) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "friendly_id_slugs", force: :cascade do |t| 19 | t.string "slug", null: false 20 | t.integer "sluggable_id", null: false 21 | t.string "sluggable_type", limit: 50 22 | t.string "scope" 23 | t.datetime "created_at" 24 | t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true 25 | t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type" 26 | t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" 27 | t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" 28 | end 29 | 30 | create_table "rooms", force: :cascade do |t| 31 | t.string "name" 32 | t.integer "status", default: 0 33 | t.datetime "created_at", null: false 34 | t.datetime "updated_at", null: false 35 | t.string "slug" 36 | t.bigint "user_id" 37 | t.string "password" 38 | t.index ["name"], name: "index_rooms_on_name", unique: true 39 | t.index ["slug"], name: "index_rooms_on_slug", unique: true 40 | t.index ["user_id"], name: "index_rooms_on_user_id" 41 | end 42 | 43 | create_table "users", force: :cascade do |t| 44 | t.string "email", default: "", null: false 45 | t.string "encrypted_password", default: "", null: false 46 | t.string "reset_password_token" 47 | t.datetime "reset_password_sent_at" 48 | t.datetime "remember_created_at" 49 | t.integer "sign_in_count", default: 0, null: false 50 | t.datetime "current_sign_in_at" 51 | t.datetime "last_sign_in_at" 52 | t.inet "current_sign_in_ip" 53 | t.inet "last_sign_in_ip" 54 | t.datetime "created_at", null: false 55 | t.datetime "updated_at", null: false 56 | t.string "provider", default: "email", null: false 57 | t.string "uid", default: "", null: false 58 | t.text "tokens" 59 | t.index ["email"], name: "index_users_on_email", unique: true 60 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 61 | t.index ["uid", "provider"], name: "index_users_on_uid_and_provider", unique: true 62 | end 63 | 64 | add_foreign_key "rooms", "users" 65 | end 66 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: video-chat-app_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: video-chat-app 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: video-chat-app_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: video-chat-app_production 84 | username: video-chat-app 85 | password: <%= ENV['VIDEO-CHAT-APP_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /config/initializers/friendly_id.rb: -------------------------------------------------------------------------------- 1 | # FriendlyId Global Configuration 2 | # 3 | # Use this to set up shared configuration options for your entire application. 4 | # Any of the configuration options shown here can also be applied to single 5 | # models by passing arguments to the `friendly_id` class method or defining 6 | # methods in your model. 7 | # 8 | # To learn more, check out the guide: 9 | # 10 | # http://norman.github.io/friendly_id/file.Guide.html 11 | 12 | FriendlyId.defaults do |config| 13 | # ## Reserved Words 14 | # 15 | # Some words could conflict with Rails's routes when used as slugs, or are 16 | # undesirable to allow as slugs. Edit this list as needed for your app. 17 | config.use :reserved 18 | 19 | config.reserved_words = %w(new edit index session login logout users admin 20 | stylesheets assets javascripts images) 21 | 22 | # ## Friendly Finders 23 | # 24 | # Uncomment this to use friendly finders in all models. By default, if 25 | # you wish to find a record by its friendly id, you must do: 26 | # 27 | # MyModel.friendly.find('foo') 28 | # 29 | # If you uncomment this, you can do: 30 | # 31 | # MyModel.find('foo') 32 | # 33 | # This is significantly more convenient but may not be appropriate for 34 | # all applications, so you must explicity opt-in to this behavior. You can 35 | # always also configure it on a per-model basis if you prefer. 36 | # 37 | # Something else to consider is that using the :finders addon boosts 38 | # performance because it will avoid Rails-internal code that makes runtime 39 | # calls to `Module.extend`. 40 | # 41 | # config.use :finders 42 | # 43 | # ## Slugs 44 | # 45 | # Most applications will use the :slugged module everywhere. If you wish 46 | # to do so, uncomment the following line. 47 | # 48 | # config.use :slugged 49 | # 50 | # By default, FriendlyId's :slugged addon expects the slug column to be named 51 | # 'slug', but you can change it if you wish. 52 | # 53 | # config.slug_column = 'slug' 54 | # 55 | # By default, slug has no size limit, but you can change it if you wish. 56 | # 57 | # config.slug_limit = 255 58 | # 59 | # When FriendlyId can not generate a unique ID from your base method, it appends 60 | # a UUID, separated by a single dash. You can configure the character used as the 61 | # separator. If you're upgrading from FriendlyId 4, you may wish to replace this 62 | # with two dashes. 63 | # 64 | # config.sequence_separator = '-' 65 | # 66 | # Note that you must use the :slugged addon **prior** to the line which 67 | # configures the sequence separator, or else FriendlyId will raise an undefined 68 | # method error. 69 | # 70 | # ## Tips and Tricks 71 | # 72 | # ### Controlling when slugs are generated 73 | # 74 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 75 | # nil, but if you're using a column as your base method can change this 76 | # behavior by overriding the `should_generate_new_friendly_id?` method that 77 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 78 | # more like 4.0. 79 | # 80 | # config.use Module.new { 81 | # def should_generate_new_friendly_id? 82 | # slug.blank? || _changed? 83 | # end 84 | # } 85 | # 86 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for 87 | # languages that don't use the Roman alphabet, that's not usually sufficient. 88 | # Here we use the Babosa library to transliterate Russian Cyrillic slugs to 89 | # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. 90 | # 91 | # config.use Module.new { 92 | # def normalize_friendly_id(text) 93 | # text.to_slug.normalize! :transliterations => [:russian, :latin] 94 | # end 95 | # } 96 | end 97 | -------------------------------------------------------------------------------- /app/javascript/bundles/src/components/HomeForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import axios from "axios"; 3 | 4 | class HomeForm extends Component { 5 | state = { 6 | post: {}, 7 | roomName: "", 8 | willCreateRoom: true 9 | }; 10 | 11 | handleButtonPress = () => { 12 | const requestConfig = { 13 | name: this.state.roomName, 14 | responseType: "json", 15 | headers: ReactOnRails.authenticityHeaders() 16 | }; 17 | 18 | if (this.state.roomName.length === 0) { 19 | return this.setState({ errorMessage: "SMH - That's not a room 😔" }); 20 | } 21 | 22 | if (this.state.willCreateRoom) { 23 | axios 24 | .post("rooms.json", requestConfig) 25 | .then(() => { 26 | return (window.location = `/rooms/${this.state.roomName}`); 27 | }) 28 | .catch(() => 29 | this.setState({ errorMessage: "Oops! Something went wrong." }) 30 | ); 31 | } else { 32 | window.location = `/rooms/${this.createSlug()}`; 33 | } 34 | }; 35 | 36 | handleToggleForm = () => 37 | this.setState({ willCreateRoom: !this.state.willCreateRoom }); 38 | 39 | createSlug = () => { 40 | const regexSpaces = /\s/g; 41 | const regexChars = /\W/g; 42 | let { roomName } = this.state; 43 | let slug = roomName.replace(regexSpaces, "-"); 44 | slug = slug.replace(regexChars, "-"); 45 | 46 | return slug; 47 | }; 48 | 49 | render() { 50 | const { willCreateRoom } = this.state; 51 | // refactor errors into its own component 52 | return ( 53 |
    54 | {this.state.errorMessage && 55 |
    56 | 65 |
    66 | {this.state.errorMessage} 67 |
    68 |
    } 69 | 70 |
    71 |
    72 |

    73 | {"Enter Room Name"} 74 |

    75 | 76 | {this.state.roomName && 77 |
    78 | {`${window.location.origin}/rooms/${this.createSlug()}`} 79 |
    } 80 | 81 |
    82 | this.setState({ roomName: e.target.value })} 88 | /> 89 |
    90 | 91 |
    92 | 98 |
    99 | 100 | 108 |
    109 |
    110 |
    111 | ); 112 | } 113 | } 114 | 115 | export default HomeForm; 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rails Video Chat App 2 | 3 | ![WebRTC on Rails](https://cdn-images-1.medium.com/max/2000/1*pwyh-wJ4clwB3s4eOXHjqw.jpeg) 4 | 5 | [https://webrtc-on-rails.herokuapp.com](https://webrtc-on-rails.herokuapp.com) 6 | 7 | # TODO 8 | 9 | - [ ] __WebRTC Support across different browsers__ 10 | - [ ] Test coverage for WebRTC JS stuff 11 | - [ ] Front End UI 12 | - [ ] React on Rails is integrated - need to make UI components for some items. maybe some of the forms? 13 | - [ ] Figure Out TurboLinks stuff 14 | - [ ] Create system tests 15 | - [ ] Create API + API tests 16 | - [ ] Error handling for non-existent pages (web + api level) 17 | - [ ] Handle web rtc connection when one peer closes browser 18 | - [ ] **Warn users on iOS mobile devices that this app won't work. React-native app coming soon** (also, app doesn't work the greatest in firefox. oof) 19 | 20 | ## As a user, I can... 21 | 22 | - [x] create a room on the fly 23 | - [x] Enter into an existing room 24 | - [ ] __[Mute my microphone](https://stackoverflow.com/questions/35512314/how-to-mute-unmute-mic-in-webrtc)__ 25 | - [ ] __Turn my video off__ 26 | 27 | ## As a guest, I can... 28 | 29 | - [x] __Create temporary rooms that expire in 24 hours__ (rake task) 30 | - [x] "register now to claim this room" 31 | 32 | ## As a member, I can... 33 | 34 | - [x] Claim a room name 35 | - [x] __Password protect my room - some kind of authorization__ 36 | - [ ] __Choose a brand color for room__ 37 | 38 | --- 39 | 40 | # API 41 | 42 | [Authentication Using Devise Token Auth](https://github.com/lynndylanhurley/devise_token_auth) 43 | 44 | # Users 45 | 46 | ## Registration 47 | 48 | __POST__ Endpoint: 49 | 50 | ``` 51 | https://webrtc-on-rails.herokuapp.com/api/v1/auth 52 | ``` 53 | 54 | Request Body: 55 | 56 | ```json 57 | { 58 | "email": "kramer@rails.com", 59 | "password": "foobar", 60 | "password_confirmation": "foobar" 61 | } 62 | ``` 63 | 64 | 200 - Response Body: 65 | 66 | ```json 67 | { 68 | "status": "success", 69 | "data": { 70 | "id": 1, 71 | "email": "kramer@rails.com", 72 | "provider": "email", 73 | "uid": "kramer@rails.com", 74 | "created_at": "2017-10-06T05:22:49.774Z", 75 | "updated_at": "2017-10-06T05:22:49.998Z" 76 | } 77 | } 78 | ``` 79 | 80 | 200 - Response Headers: 81 | __(these must be passed along every authenticated request)__ 82 | 83 | ``` 84 | access-token → fdBnuoN8VtlOLLW-cKB58Q 85 | token-type → Bearer 86 | client → ucLUd-EIkM-ETYLWFSIL4w 87 | expiry → 1508475926 88 | uid → kramer@rails.com 89 | ``` 90 | 91 | 422 - Response Body: 92 | 93 | ```json 94 | { 95 | "status": "error", 96 | "data": { 97 | "id": null, 98 | "email": "kramer@rails.com", 99 | "created_at": null, 100 | "updated_at": null, 101 | "provider": "email", 102 | "uid": "kramer@rails.com" 103 | }, 104 | "errors": { 105 | "email": [ 106 | "has already been taken" 107 | ], 108 | "full_messages": [ 109 | "Email has already been taken" 110 | ] 111 | } 112 | } 113 | ``` 114 | 115 | ## Signing In 116 | 117 | POST Endpoint: 118 | 119 | ``` 120 | https://webrtc-on-rails.herokuapp.com/api/v1/auth/sign_in 121 | ``` 122 | 123 | Request Body: 124 | 125 | ```json 126 | { 127 | "email": "kramer@rails.com", 128 | "password": "foobar" 129 | } 130 | ``` 131 | 132 | 200 - Response Body: 133 | 134 | ```json 135 | { 136 | "data": { 137 | "id": 1, 138 | "email": "kramer@rails.com", 139 | "provider": "email", 140 | "uid": "kramer@rails.com" 141 | } 142 | } 143 | ``` 144 | 145 | 200 - Response Headers: 146 | __(these must be passed along every authenticated request)__ 147 | 148 | ``` 149 | access-token → fdBnuoN8VtlOLLW-cKB58Q 150 | token-type → Bearer 151 | client → ucLUd-EIkM-ETYLWFSIL4w 152 | expiry → 1508475926 153 | uid → kramer@rails.com 154 | ``` 155 | 156 | 401 - Response Body: 157 | 158 | ```json 159 | { 160 | "errors": [ 161 | "Invalid login credentials. Please try again." 162 | ] 163 | } 164 | ``` 165 | -------------------------------------------------------------------------------- /app/controllers/rooms_controller.rb: -------------------------------------------------------------------------------- 1 | require 'rest-client' 2 | require 'json' 3 | 4 | # :nodoc: 5 | class RoomsController < ApplicationController 6 | before_action :set_user 7 | before_action :authenticate_user!, only: %i[index edit new update] 8 | before_action :set_room, only: %i[show edit update destroy 9 | toggle_status claim authenticate] 10 | rescue_from ActiveRecord::RecordNotFound, with: :room_not_found 11 | rescue_from SocketError, with: :socket_error 12 | 13 | def authenticate 14 | password_hash = @room.check_hashed_password(@room.password) 15 | 16 | if password_hash == params[:password] 17 | flash[:success] = "You are now in the room: #{@room.name}" 18 | redirect_to action: 'show', password: password_hash 19 | else 20 | flash[:notice] = 'You entered the incorrect password!' 21 | end 22 | end 23 | 24 | def index 25 | @rooms = @user.rooms 26 | end 27 | 28 | def new 29 | @room = Room.new 30 | end 31 | 32 | # TODO refactor this into an API controller 33 | def create 34 | @room = Room.new(room_create_params) 35 | 36 | respond_to do |format| 37 | if @room.save 38 | format.html do 39 | flash[:success] = 'You created a room!' 40 | redirect_to @room 41 | end 42 | 43 | format.json { render :show, status: :created, location: @room } 44 | else 45 | format.html do 46 | flash[:notice] = 'Sorry, that room is taken!' 47 | redirect_to root_path 48 | end 49 | 50 | format.json { render json: @room.errors, status: :unprocessable_entity } 51 | end 52 | end 53 | end 54 | 55 | def edit 56 | unless @room.user == current_user 57 | flash[:notice] = 'You cannot edit this room' 58 | redirect_to @room 59 | end 60 | end 61 | 62 | def update 63 | if @room.update_attributes(room_update_params) 64 | flash[:success] = 'You added a password to this room' 65 | else 66 | flash[:notice] = 'Something went wrong' 67 | end 68 | 69 | redirect_to @room 70 | end 71 | 72 | def show 73 | ask_for_password(@room, params[:password]) unless 74 | @room.password.nil? || 75 | @room.user == current_user 76 | 77 | if @room.password 78 | flash.now[:notice] = 'This is a password protected room' 79 | end 80 | 81 | return if Rails.env == 'test' 82 | XirsysCredentialsJob.perform_later(@room.slug) 83 | end 84 | 85 | def toggle_status 86 | if (@user.is_a? GuestUser) || 87 | (@room.user != @user) || 88 | (params[:status] == 'restricted') 89 | flash[:notice] = 'You are not allowed to do that!' 90 | redirect_to @room 91 | return 92 | end 93 | 94 | @room.status = params[:status] 95 | @room.save 96 | flash[:notice] = 'Room status updated' 97 | redirect_to @room 98 | end 99 | 100 | def claim 101 | if @room.user || (@user.is_a? GuestUser) 102 | flash[:notice] = 'You are not allowed to do that!' 103 | redirect_to @room 104 | return 105 | end 106 | 107 | claim_room 108 | end 109 | 110 | private 111 | 112 | def set_user 113 | @user = current_user 114 | end 115 | 116 | def set_room 117 | @room = Room.friendly.find(params[:id]) 118 | end 119 | 120 | def room_create_params 121 | params.require(:room).permit(:name, :user_id, :password) 122 | end 123 | 124 | def room_update_params 125 | params.require(:room).permit(:password) 126 | end 127 | 128 | def ask_for_password(room, password = nil) 129 | render 'rooms/authenticate' unless password == room.password 130 | end 131 | 132 | def claim_room 133 | @room.user = current_user 134 | @room.status = 'unrestricted' 135 | @room.save 136 | flash[:success] = 'Room claimed' 137 | redirect_to @room 138 | end 139 | 140 | def room_not_found 141 | flash[:notice] = 'That room does not exist, would you like to create it?' 142 | redirect_to new_room_path 143 | end 144 | 145 | def socket_error 146 | flash[:notice] = 'TODO: Something went wrong with the WebRTC stuff' 147 | redirect_to root_path 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | email_changed: 27 | subject: "Email Changed" 28 | password_change: 29 | subject: "Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | sessions: 48 | signed_in: "Signed in successfully." 49 | signed_out: "Signed out successfully." 50 | already_signed_out: "Signed out successfully." 51 | unlocks: 52 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 53 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 54 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 55 | errors: 56 | messages: 57 | already_confirmed: "was already confirmed, please try signing in" 58 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 59 | expired: "has expired, please request a new one" 60 | not_found: "not found" 61 | not_locked: "was not locked" 62 | not_saved: 63 | one: "1 error prohibited this %{resource} from being saved:" 64 | other: "%{count} errors prohibited this %{resource} from being saved:" 65 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`. 18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or 19 | # `config/secrets.yml.key`. 20 | config.read_encrypted_secrets = true 21 | 22 | # Disable serving static files from the `/public` folder by default since 23 | # Apache or NGINX already handles this. 24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 25 | 26 | # Compress JavaScripts and CSS. 27 | config.assets.js_compressor = :uglifier 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 34 | 35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 36 | # config.action_controller.asset_host = 'http://assets.example.com' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 41 | 42 | # Mount Action Cable outside main process or domain 43 | # config.action_cable.mount_path = nil 44 | config.action_cable.url = 'wss://webrtc-on-rails.herokuapp.com/cable' 45 | config.action_cable.allowed_request_origins = [ '*' ] 46 | 47 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 48 | config.force_ssl = true 49 | 50 | # Use the lowest log level to ensure availability of diagnostic information 51 | # when problems arise. 52 | config.log_level = :debug 53 | 54 | # Prepend all log lines with the following tags. 55 | config.log_tags = [ :request_id ] 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Use a real queuing backend for Active Job (and separate queues per environment) 61 | # config.active_job.queue_adapter = :resque 62 | # config.active_job.queue_name_prefix = "video-chat-app_#{Rails.env}" 63 | config.action_mailer.perform_caching = false 64 | 65 | config.action_mailer.raise_delivery_errors = true 66 | config.action_mailer.delivery_method = :smtp 67 | host = 'webrtc-on-rails.herokuapp.com' 68 | config.action_mailer.default_url_options = { host: host } 69 | ActionMailer::Base.smtp_settings = { 70 | address: 'smtp.sendgrid.net', 71 | port: '587', 72 | authentication: :plain, 73 | user_name: ENV['SENDGRID_USERNAME'], 74 | password: ENV['SENDGRID_PASSWORD'], 75 | domain: 'heroku.com', 76 | enable_starttls_auto: true 77 | } 78 | 79 | # Ignore bad email addresses and do not raise email delivery errors. 80 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 81 | # config.action_mailer.raise_delivery_errors = false 82 | 83 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 84 | # the I18n.default_locale when a translation cannot be found). 85 | config.i18n.fallbacks = true 86 | 87 | # Send deprecation notices to registered listeners. 88 | config.active_support.deprecation = :notify 89 | 90 | # Use default logging formatter so that PID and timestamp are not suppressed. 91 | config.log_formatter = ::Logger::Formatter.new 92 | 93 | # Use a different logger for distributed setups. 94 | # require 'syslog/logger' 95 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 96 | 97 | if ENV["RAILS_LOG_TO_STDOUT"].present? 98 | logger = ActiveSupport::Logger.new(STDOUT) 99 | logger.formatter = config.log_formatter 100 | config.logger = ActiveSupport::TaggedLogging.new(logger) 101 | end 102 | 103 | # Do not dump schema after migrations. 104 | config.active_record.dump_schema_after_migration = false 105 | end 106 | -------------------------------------------------------------------------------- /app/views/rooms/js/webrtc.js: -------------------------------------------------------------------------------- 1 | document.getElementById("copy-button").addEventListener("click", function(e) { 2 | e.preventDefault(); 3 | 4 | var copied; 5 | document.getElementById("copy-room-name").select(); 6 | 7 | try { 8 | copied = document.execCommand("copy"); 9 | } catch (e) { 10 | copied = false; 11 | } 12 | 13 | if (copied) { 14 | document.getElementById("copy-button").innerHTML = "Saved to Clipboard!" 15 | } 16 | }); 17 | 18 | // Broadcast Types 19 | const JOIN_ROOM = "JOIN_ROOM"; 20 | const EXCHANGE = "EXCHANGE"; 21 | const REMOVE_USER = "REMOVE_USER"; 22 | 23 | // DOM Elements 24 | const currentUser = document.getElementById("currentUser").dataset.email; 25 | const selfView = document.getElementById("selfView"); 26 | const remoteViewContainer = document.getElementById("remoteViewContainer"); 27 | const joinBtnContainer = document.getElementById("join-btn-container"); 28 | const leaveBtnContainer = document.getElementById("leave-btn-container"); 29 | 30 | // Configuration 31 | let roomName = document.getElementById("room-name").dataset.room; 32 | let constraints = { audio: false, video: true }; 33 | let xirsysIceCreds; 34 | 35 | // Global Objects 36 | let pcPeers = {}; 37 | let localStream; 38 | 39 | window.onload = () => { 40 | initialize(); 41 | }; 42 | 43 | const initialize = async () => { 44 | App.ice = await App.cable.subscriptions.create( 45 | { channel: "IceChannel", id: roomName }, 46 | { 47 | received: data => { 48 | xirsysIceCreds = JSON.parse(data); 49 | xirsysIceCreds = xirsysIceCreds["v"]; 50 | } 51 | } 52 | ); 53 | 54 | navigator.mediaDevices 55 | .getUserMedia(constraints) 56 | .then(stream => { 57 | localStream = stream; 58 | selfView.srcObject = stream; 59 | selfView.muted = true; 60 | }) 61 | .catch(logError); 62 | }; 63 | 64 | const handleJoinSession = async () => { 65 | App.session = await App.cable.subscriptions.create( 66 | { channel: "SessionChannel", id: roomName }, 67 | { 68 | connected: () => connectUser(currentUser), 69 | received: data => { 70 | console.log("received", data); 71 | if (data.from === currentUser) return; 72 | switch (data.type) { 73 | case JOIN_ROOM: 74 | return joinRoom(data); 75 | case EXCHANGE: 76 | if (data.to !== currentUser) return; 77 | return exchange(data); 78 | case REMOVE_USER: 79 | return removeUser(data); 80 | default: 81 | return; 82 | } 83 | } 84 | } 85 | ); 86 | 87 | joinBtnContainer.style.display = "none"; 88 | leaveBtnContainer.style.display = "block"; 89 | }; 90 | 91 | const handleLeaveSession = () => { 92 | for (user in pcPeers) { 93 | pcPeers[user].close(); 94 | } 95 | pcPeers = {}; 96 | 97 | App.session.unsubscribe(); 98 | 99 | remoteViewContainer.innerHTML = ""; 100 | 101 | broadcastData({ 102 | type: REMOVE_USER, 103 | from: currentUser, 104 | roomName 105 | }); 106 | 107 | joinBtnContainer.style.display = "block"; 108 | leaveBtnContainer.style.display = "none"; 109 | }; 110 | 111 | const connectUser = userId => { 112 | broadcastData({ 113 | type: JOIN_ROOM, 114 | from: currentUser, 115 | roomName 116 | }); 117 | }; 118 | 119 | const joinRoom = data => { 120 | createPC(data.from, true); 121 | }; 122 | 123 | const removeUser = data => { 124 | console.log("removing user", data.from); 125 | let video = document.getElementById(`remoteView+${data.from}`); 126 | video && video.remove(); 127 | delete pcPeers[data.from]; 128 | }; 129 | 130 | const createPC = (userId, isOffer) => { 131 | let pc = new RTCPeerConnection(xirsysIceCreds); 132 | pcPeers[userId] = pc; 133 | pc.addStream(localStream); 134 | 135 | isOffer && 136 | pc 137 | .createOffer() 138 | .then(offer => { 139 | pc.setLocalDescription(offer); 140 | broadcastData({ 141 | type: EXCHANGE, 142 | from: currentUser, 143 | to: userId, 144 | sdp: JSON.stringify(pc.localDescription), 145 | roomName 146 | }); 147 | }) 148 | .catch(logError); 149 | 150 | pc.onicecandidate = event => { 151 | event.candidate && 152 | broadcastData({ 153 | type: EXCHANGE, 154 | from: currentUser, 155 | to: userId, 156 | candidate: JSON.stringify(event.candidate), 157 | roomName 158 | }); 159 | }; 160 | 161 | pc.onaddstream = event => { 162 | const element = document.createElement("video"); 163 | element.id = `remoteView+${userId}`; 164 | element.autoplay = "autoplay"; 165 | element.srcObject = event.stream; 166 | remoteViewContainer.appendChild(element); 167 | }; 168 | 169 | pc.oniceconnectionstatechange = event => { 170 | if (pc.iceConnectionState == "disconnected") { 171 | console.log("Disconnected:", userId); 172 | broadcastData({ 173 | type: REMOVE_USER, 174 | from: userId, 175 | roomName 176 | }); 177 | } 178 | }; 179 | 180 | return pc; 181 | }; 182 | 183 | const exchange = data => { 184 | let pc; 185 | 186 | if (!pcPeers[data.from]) { 187 | pc = createPC(data.from, false); 188 | } else { 189 | pc = pcPeers[data.from]; 190 | } 191 | 192 | if (data.candidate) { 193 | pc 194 | .addIceCandidate(new RTCIceCandidate(JSON.parse(data.candidate))) 195 | .then(() => console.log("Ice candidate added")) 196 | .catch(logError); 197 | } 198 | 199 | if (data.sdp) { 200 | sdp = JSON.parse(data.sdp); 201 | pc 202 | .setRemoteDescription(new RTCSessionDescription(sdp)) 203 | .then(() => { 204 | if (sdp.type === "offer") { 205 | pc.createAnswer().then(answer => { 206 | pc.setLocalDescription(answer); 207 | broadcastData({ 208 | type: EXCHANGE, 209 | from: currentUser, 210 | to: data.from, 211 | sdp: JSON.stringify(pc.localDescription), 212 | roomName 213 | }); 214 | }); 215 | } 216 | }) 217 | .catch(logError); 218 | } 219 | }; 220 | 221 | const broadcastData = data => { 222 | $.ajax({ 223 | url: "/sessions", 224 | type: "post", 225 | data 226 | }); 227 | }; 228 | 229 | const logError = error => console.warn("Error:", error); 230 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.1.4) 5 | actionpack (= 5.1.4) 6 | nio4r (~> 2.0) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.1.4) 9 | actionpack (= 5.1.4) 10 | actionview (= 5.1.4) 11 | activejob (= 5.1.4) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.1.4) 15 | actionview (= 5.1.4) 16 | activesupport (= 5.1.4) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.1.4) 22 | activesupport (= 5.1.4) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.1.4) 28 | activesupport (= 5.1.4) 29 | globalid (>= 0.3.6) 30 | activemodel (5.1.4) 31 | activesupport (= 5.1.4) 32 | activerecord (5.1.4) 33 | activemodel (= 5.1.4) 34 | activesupport (= 5.1.4) 35 | arel (~> 8.0) 36 | activesupport (5.1.4) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (~> 0.7) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | addressable (2.5.2) 42 | public_suffix (>= 2.0.2, < 4.0) 43 | ansi (1.5.0) 44 | arel (8.0.0) 45 | autoprefixer-rails (7.1.4.1) 46 | execjs 47 | bcrypt (3.1.11) 48 | bindex (0.5.0) 49 | bootstrap (4.0.0.beta) 50 | autoprefixer-rails (>= 6.0.3) 51 | popper_js (~> 1.11.1) 52 | sass (>= 3.4.19) 53 | builder (3.2.3) 54 | byebug (9.1.0) 55 | capybara (2.15.2) 56 | addressable 57 | mini_mime (>= 0.1.3) 58 | nokogiri (>= 1.3.3) 59 | rack (>= 1.0.0) 60 | rack-test (>= 0.5.4) 61 | xpath (~> 2.0) 62 | childprocess (0.8.0) 63 | ffi (~> 1.0, >= 1.0.11) 64 | coderay (1.1.2) 65 | coffee-rails (4.2.2) 66 | coffee-script (>= 2.2.0) 67 | railties (>= 4.0.0) 68 | coffee-script (2.4.1) 69 | coffee-script-source 70 | execjs 71 | coffee-script-source (1.12.2) 72 | concurrent-ruby (1.0.5) 73 | connection_pool (2.2.1) 74 | crass (1.0.2) 75 | devise (4.3.0) 76 | bcrypt (~> 3.0) 77 | orm_adapter (~> 0.1) 78 | railties (>= 4.1.0, < 5.2) 79 | responders 80 | warden (~> 1.2.3) 81 | devise_token_auth (0.1.42) 82 | devise (> 3.5.2, <= 4.3) 83 | rails (< 6) 84 | domain_name (0.5.20170404) 85 | unf (>= 0.0.5, < 1.0.0) 86 | erubi (1.6.1) 87 | execjs (2.7.0) 88 | ffi (1.9.18) 89 | figaro (1.1.1) 90 | thor (~> 0.14) 91 | formatador (0.2.5) 92 | friendly_id (5.2.3) 93 | activerecord (>= 4.0.0) 94 | globalid (0.4.0) 95 | activesupport (>= 4.2.0) 96 | guard (2.14.1) 97 | formatador (>= 0.2.4) 98 | listen (>= 2.7, < 4.0) 99 | lumberjack (~> 1.0) 100 | nenv (~> 0.1) 101 | notiffany (~> 0.0) 102 | pry (>= 0.9.12) 103 | shellany (~> 0.0) 104 | thor (>= 0.18.1) 105 | guard-compat (1.2.1) 106 | guard-minitest (2.4.6) 107 | guard-compat (~> 1.2) 108 | minitest (>= 3.0) 109 | hashie (3.5.6) 110 | http-cookie (1.0.3) 111 | domain_name (~> 0.5) 112 | i18n (0.8.6) 113 | jbuilder (2.7.0) 114 | activesupport (>= 4.2.0) 115 | multi_json (>= 1.2) 116 | jquery-rails (4.3.1) 117 | rails-dom-testing (>= 1, < 3) 118 | railties (>= 4.2.0) 119 | thor (>= 0.14, < 2.0) 120 | json (2.1.0) 121 | libv8 (5.9.211.38.1-x86_64-darwin-16) 122 | listen (3.1.5) 123 | rb-fsevent (~> 0.9, >= 0.9.4) 124 | rb-inotify (~> 0.9, >= 0.9.7) 125 | ruby_dep (~> 1.2) 126 | loofah (2.1.1) 127 | crass (~> 1.0.2) 128 | nokogiri (>= 1.5.9) 129 | lumberjack (1.0.12) 130 | mail (2.6.6) 131 | mime-types (>= 1.16, < 4) 132 | method_source (0.9.0) 133 | mime-types (3.1) 134 | mime-types-data (~> 3.2015) 135 | mime-types-data (3.2016.0521) 136 | mini_mime (0.1.4) 137 | mini_portile2 (2.3.0) 138 | mini_racer (0.1.12) 139 | libv8 (~> 5.9) 140 | minitest (5.10.3) 141 | minitest-reporters (1.1.18) 142 | ansi 143 | builder 144 | minitest (>= 5.0) 145 | ruby-progressbar 146 | multi_json (1.12.2) 147 | nenv (0.3.0) 148 | netrc (0.11.0) 149 | nio4r (2.1.0) 150 | nokogiri (1.8.1) 151 | mini_portile2 (~> 2.3.0) 152 | notiffany (0.1.1) 153 | nenv (~> 0.1) 154 | shellany (~> 0.0) 155 | omniauth (1.7.0) 156 | hashie (>= 3.4.6, < 3.6.0) 157 | rack (>= 1.6.2, < 3) 158 | orm_adapter (0.5.0) 159 | pg (0.21.0) 160 | popper_js (1.11.1) 161 | pry (0.11.1) 162 | coderay (~> 1.1.0) 163 | method_source (~> 0.9.0) 164 | public_suffix (3.0.0) 165 | puma (3.10.0) 166 | rack (2.0.3) 167 | rack-cors (0.4.1) 168 | rack-proxy (0.6.2) 169 | rack 170 | rack-test (0.7.0) 171 | rack (>= 1.0, < 3) 172 | rails (5.1.4) 173 | actioncable (= 5.1.4) 174 | actionmailer (= 5.1.4) 175 | actionpack (= 5.1.4) 176 | actionview (= 5.1.4) 177 | activejob (= 5.1.4) 178 | activemodel (= 5.1.4) 179 | activerecord (= 5.1.4) 180 | activesupport (= 5.1.4) 181 | bundler (>= 1.3.0) 182 | railties (= 5.1.4) 183 | sprockets-rails (>= 2.0.0) 184 | rails-controller-testing (1.0.2) 185 | actionpack (~> 5.x, >= 5.0.1) 186 | actionview (~> 5.x, >= 5.0.1) 187 | activesupport (~> 5.x) 188 | rails-dom-testing (2.0.3) 189 | activesupport (>= 4.2.0) 190 | nokogiri (>= 1.6) 191 | rails-html-sanitizer (1.0.3) 192 | loofah (~> 2.0) 193 | rails_real_favicon (0.0.7) 194 | json (>= 1.7, < 3) 195 | rails (>= 3.1) 196 | rest-client (~> 2.0) 197 | rubyzip (~> 1) 198 | railties (5.1.4) 199 | actionpack (= 5.1.4) 200 | activesupport (= 5.1.4) 201 | method_source 202 | rake (>= 0.8.7) 203 | thor (>= 0.18.1, < 2.0) 204 | rainbow (2.2.2) 205 | rake 206 | rake (12.1.0) 207 | rb-fsevent (0.10.2) 208 | rb-inotify (0.9.10) 209 | ffi (>= 0.5.0, < 2) 210 | react_on_rails (9.0.0) 211 | addressable 212 | connection_pool 213 | execjs (~> 2.5) 214 | rails (>= 3.2) 215 | rainbow (~> 2.2) 216 | redis (3.3.3) 217 | responders (2.4.0) 218 | actionpack (>= 4.2.0, < 5.3) 219 | railties (>= 4.2.0, < 5.3) 220 | rest-client (2.0.2) 221 | http-cookie (>= 1.0.2, < 2.0) 222 | mime-types (>= 1.16, < 4.0) 223 | netrc (~> 0.8) 224 | ruby-progressbar (1.9.0) 225 | ruby_dep (1.5.0) 226 | rubyzip (1.2.1) 227 | sass (3.5.1) 228 | sass-listen (~> 4.0.0) 229 | sass-listen (4.0.0) 230 | rb-fsevent (~> 0.9, >= 0.9.4) 231 | rb-inotify (~> 0.9, >= 0.9.7) 232 | sass-rails (5.0.6) 233 | railties (>= 4.0.0, < 6) 234 | sass (~> 3.1) 235 | sprockets (>= 2.8, < 4.0) 236 | sprockets-rails (>= 2.0, < 4.0) 237 | tilt (>= 1.1, < 3) 238 | selenium-webdriver (3.6.0) 239 | childprocess (~> 0.5) 240 | rubyzip (~> 1.0) 241 | shellany (0.0.1) 242 | spring (2.0.2) 243 | activesupport (>= 4.2) 244 | spring-watcher-listen (2.0.1) 245 | listen (>= 2.7, < 4.0) 246 | spring (>= 1.2, < 3.0) 247 | sprockets (3.7.1) 248 | concurrent-ruby (~> 1.0) 249 | rack (> 1, < 3) 250 | sprockets-rails (3.2.1) 251 | actionpack (>= 4.0) 252 | activesupport (>= 4.0) 253 | sprockets (>= 3.0.0) 254 | thor (0.20.0) 255 | thread_safe (0.3.6) 256 | tilt (2.0.8) 257 | turbolinks (5.0.1) 258 | turbolinks-source (~> 5) 259 | turbolinks-source (5.0.3) 260 | tzinfo (1.2.3) 261 | thread_safe (~> 0.1) 262 | uglifier (3.2.0) 263 | execjs (>= 0.3.0, < 3) 264 | unf (0.1.4) 265 | unf_ext 266 | unf_ext (0.0.7.4) 267 | warden (1.2.7) 268 | rack (>= 1.0) 269 | web-console (3.5.1) 270 | actionview (>= 5.0) 271 | activemodel (>= 5.0) 272 | bindex (>= 0.4.0) 273 | railties (>= 5.0) 274 | webpacker (3.0.2) 275 | activesupport (>= 4.2) 276 | rack-proxy (>= 0.6.1) 277 | railties (>= 4.2) 278 | websocket-driver (0.6.5) 279 | websocket-extensions (>= 0.1.0) 280 | websocket-extensions (0.1.2) 281 | xpath (2.1.0) 282 | nokogiri (~> 1.3) 283 | 284 | PLATFORMS 285 | ruby 286 | 287 | DEPENDENCIES 288 | bcrypt (~> 3.1, >= 3.1.11) 289 | bootstrap (~> 4.0.0.beta) 290 | byebug 291 | capybara (~> 2.13) 292 | coffee-rails (~> 4.2) 293 | devise (~> 4.3) 294 | devise_token_auth (~> 0.1.42) 295 | figaro (~> 1.1, >= 1.1.1) 296 | friendly_id (~> 5.2, >= 5.2.3) 297 | guard (~> 2.14, >= 2.14.1) 298 | guard-minitest (~> 2.4, >= 2.4.6) 299 | jbuilder (~> 2.5) 300 | jquery-rails (~> 4.3, >= 4.3.1) 301 | json (~> 2.1) 302 | listen (>= 3.0.5, < 3.2) 303 | mini_racer 304 | minitest-reporters (~> 1.1, >= 1.1.18) 305 | omniauth (~> 1.7) 306 | pg (~> 0.18) 307 | puma (~> 3.7) 308 | rack-cors (~> 0.4.1) 309 | rails (~> 5.1.4) 310 | rails-controller-testing (~> 1.0, >= 1.0.2) 311 | rails_real_favicon 312 | react_on_rails (= 9.0.0) 313 | redis (~> 3.0) 314 | rest-client (~> 2.0, >= 2.0.2) 315 | sass-rails (~> 5.0) 316 | selenium-webdriver 317 | spring 318 | spring-watcher-listen (~> 2.0.0) 319 | turbolinks (~> 5) 320 | tzinfo-data 321 | uglifier (>= 1.3.0) 322 | web-console (>= 3.3.0) 323 | webpacker (~> 3.0) 324 | 325 | BUNDLED WITH 326 | 1.14.6 327 | -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. 2 | # Many of these configuration options can be set straight in your model. 3 | Devise.setup do |config| 4 | # The secret key used by Devise. Devise uses this key to generate 5 | # random tokens. Changing this key will render invalid all existing 6 | # confirmation, reset password and unlock tokens in the database. 7 | # Devise will use the `secret_key_base` as its `secret_key` 8 | # by default. You can change it below and use your own secret key. 9 | # config.secret_key = '8f334c7a5e8468fc0a9d136d873b695658ecf6d03d829609cf0ca3276b721aed13863e784e89eed5ce19a8c8c83d946ad5f43ee089cdc5c2ba7086938a190519' 10 | 11 | # ==> Mailer Configuration 12 | # Configure the e-mail address which will be shown in Devise::Mailer, 13 | # note that it will be overwritten if you use your own mailer class 14 | # with default "from" parameter. 15 | config.mailer_sender = 'admin@webrtc-on-rails.herokuapp.com' 16 | 17 | # Configure the class responsible to send e-mails. 18 | # config.mailer = 'Devise::Mailer' 19 | 20 | # Configure the parent class responsible to send e-mails. 21 | # config.parent_mailer = 'ActionMailer::Base' 22 | 23 | # ==> ORM configuration 24 | # Load and configure the ORM. Supports :active_record (default) and 25 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 26 | # available as additional gems. 27 | require 'devise/orm/active_record' 28 | 29 | # ==> Configuration for any authentication mechanism 30 | # Configure which keys are used when authenticating a user. The default is 31 | # just :email. You can configure it to use [:username, :subdomain], so for 32 | # authenticating a user, both parameters are required. Remember that those 33 | # parameters are used only when authenticating and not when retrieving from 34 | # session. If you need permissions, you should implement that in a before filter. 35 | # You can also supply a hash where the value is a boolean determining whether 36 | # or not authentication should be aborted when the value is not present. 37 | # config.authentication_keys = [:email] 38 | 39 | # Configure parameters from the request object used for authentication. Each entry 40 | # given should be a request method and it will automatically be passed to the 41 | # find_for_authentication method and considered in your model lookup. For instance, 42 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 43 | # The same considerations mentioned for authentication_keys also apply to request_keys. 44 | # config.request_keys = [] 45 | 46 | # Configure which authentication keys should be case-insensitive. 47 | # These keys will be downcased upon creating or modifying a user and when used 48 | # to authenticate or find a user. Default is :email. 49 | config.case_insensitive_keys = [:email] 50 | 51 | # Configure which authentication keys should have whitespace stripped. 52 | # These keys will have whitespace before and after removed upon creating or 53 | # modifying a user and when used to authenticate or find a user. Default is :email. 54 | config.strip_whitespace_keys = [:email] 55 | 56 | # Tell if authentication through request.params is enabled. True by default. 57 | # It can be set to an array that will enable params authentication only for the 58 | # given strategies, for example, `config.params_authenticatable = [:database]` will 59 | # enable it only for database (email + password) authentication. 60 | # config.params_authenticatable = true 61 | 62 | # Tell if authentication through HTTP Auth is enabled. False by default. 63 | # It can be set to an array that will enable http authentication only for the 64 | # given strategies, for example, `config.http_authenticatable = [:database]` will 65 | # enable it only for database authentication. The supported strategies are: 66 | # :database = Support basic authentication with authentication key + password 67 | # config.http_authenticatable = false 68 | 69 | # If 401 status code should be returned for AJAX requests. True by default. 70 | # config.http_authenticatable_on_xhr = true 71 | 72 | # The realm used in Http Basic Authentication. 'Application' by default. 73 | # config.http_authentication_realm = 'Application' 74 | 75 | # It will change confirmation, password recovery and other workflows 76 | # to behave the same regardless if the e-mail provided was right or wrong. 77 | # Does not affect registerable. 78 | # config.paranoid = true 79 | 80 | # By default Devise will store the user in session. You can skip storage for 81 | # particular strategies by setting this option. 82 | # Notice that if you are skipping storage for all authentication paths, you 83 | # may want to disable generating routes to Devise's sessions controller by 84 | # passing skip: :sessions to `devise_for` in your config/routes.rb 85 | config.skip_session_storage = [:http_auth] 86 | 87 | # By default, Devise cleans up the CSRF token on authentication to 88 | # avoid CSRF token fixation attacks. This means that, when using AJAX 89 | # requests for sign in and sign up, you need to get a new CSRF token 90 | # from the server. You can disable this option at your own risk. 91 | # config.clean_up_csrf_token_on_authentication = true 92 | 93 | # When false, Devise will not attempt to reload routes on eager load. 94 | # This can reduce the time taken to boot the app but if your application 95 | # requires the Devise mappings to be loaded during boot time the application 96 | # won't boot properly. 97 | # config.reload_routes = true 98 | 99 | # ==> Configuration for :database_authenticatable 100 | # For bcrypt, this is the cost for hashing the password and defaults to 11. If 101 | # using other algorithms, it sets how many times you want the password to be hashed. 102 | # 103 | # Limiting the stretches to just one in testing will increase the performance of 104 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 105 | # a value less than 10 in other environments. Note that, for bcrypt (the default 106 | # algorithm), the cost increases exponentially with the number of stretches (e.g. 107 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). 108 | config.stretches = Rails.env.test? ? 1 : 11 109 | 110 | # Set up a pepper to generate the hashed password. 111 | # config.pepper = 'd0ebcc0d77eba7066d2265c487ea033d624a07c45af996c7701a70411af3378fcc79d7aaa751b97d7dcce3fcb4622515a4d2416b2b5b2b5f78f4013af8023dfb' 112 | 113 | # Send a notification to the original email when the user's email is changed. 114 | # config.send_email_changed_notification = false 115 | 116 | # Send a notification email when the user's password is changed. 117 | # config.send_password_change_notification = false 118 | 119 | # ==> Configuration for :confirmable 120 | # A period that the user is allowed to access the website even without 121 | # confirming their account. For instance, if set to 2.days, the user will be 122 | # able to access the website for two days without confirming their account, 123 | # access will be blocked just in the third day. Default is 0.days, meaning 124 | # the user cannot access the website without confirming their account. 125 | # config.allow_unconfirmed_access_for = 2.days 126 | 127 | # A period that the user is allowed to confirm their account before their 128 | # token becomes invalid. For example, if set to 3.days, the user can confirm 129 | # their account within 3 days after the mail was sent, but on the fourth day 130 | # their account can't be confirmed with the token any more. 131 | # Default is nil, meaning there is no restriction on how long a user can take 132 | # before confirming their account. 133 | # config.confirm_within = 3.days 134 | 135 | # If true, requires any email changes to be confirmed (exactly the same way as 136 | # initial account confirmation) to be applied. Requires additional unconfirmed_email 137 | # db field (see migrations). Until confirmed, new email is stored in 138 | # unconfirmed_email column, and copied to email column on successful confirmation. 139 | config.reconfirmable = true 140 | 141 | # Defines which key will be used when confirming an account 142 | # config.confirmation_keys = [:email] 143 | 144 | # ==> Configuration for :rememberable 145 | # The time the user will be remembered without asking for credentials again. 146 | # config.remember_for = 2.weeks 147 | 148 | # Invalidates all the remember me tokens when the user signs out. 149 | config.expire_all_remember_me_on_sign_out = true 150 | 151 | # If true, extends the user's remember period when remembered via cookie. 152 | # config.extend_remember_period = false 153 | 154 | # Options to be passed to the created cookie. For instance, you can set 155 | # secure: true in order to force SSL only cookies. 156 | # config.rememberable_options = {} 157 | 158 | # ==> Configuration for :validatable 159 | # Range for password length. 160 | config.password_length = 6..128 161 | 162 | # Email regex used to validate email formats. It simply asserts that 163 | # one (and only one) @ exists in the given string. This is mainly 164 | # to give user feedback and not to assert the e-mail validity. 165 | config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ 166 | 167 | # ==> Configuration for :timeoutable 168 | # The time you want to timeout the user session without activity. After this 169 | # time the user will be asked for credentials again. Default is 30 minutes. 170 | # config.timeout_in = 30.minutes 171 | 172 | # ==> Configuration for :lockable 173 | # Defines which strategy will be used to lock an account. 174 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 175 | # :none = No lock strategy. You should handle locking by yourself. 176 | # config.lock_strategy = :failed_attempts 177 | 178 | # Defines which key will be used when locking and unlocking an account 179 | # config.unlock_keys = [:email] 180 | 181 | # Defines which strategy will be used to unlock an account. 182 | # :email = Sends an unlock link to the user email 183 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 184 | # :both = Enables both strategies 185 | # :none = No unlock strategy. You should handle unlocking by yourself. 186 | # config.unlock_strategy = :both 187 | 188 | # Number of authentication tries before locking an account if lock_strategy 189 | # is failed attempts. 190 | # config.maximum_attempts = 20 191 | 192 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 193 | # config.unlock_in = 1.hour 194 | 195 | # Warn on the last attempt before the account is locked. 196 | # config.last_attempt_warning = true 197 | 198 | # ==> Configuration for :recoverable 199 | # 200 | # Defines which key will be used when recovering the password for an account 201 | # config.reset_password_keys = [:email] 202 | 203 | # Time interval you can reset your password with a reset password key. 204 | # Don't put a too small interval or your users won't have the time to 205 | # change their passwords. 206 | config.reset_password_within = 6.hours 207 | 208 | # When set to false, does not sign a user in automatically after their password is 209 | # reset. Defaults to true, so a user is signed in automatically after a reset. 210 | # config.sign_in_after_reset_password = true 211 | 212 | # ==> Configuration for :encryptable 213 | # Allow you to use another hashing or encryption algorithm besides bcrypt (default). 214 | # You can use :sha1, :sha512 or algorithms from others authentication tools as 215 | # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 216 | # for default behavior) and :restful_authentication_sha1 (then you should set 217 | # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). 218 | # 219 | # Require the `devise-encryptable` gem when using anything other than bcrypt 220 | # config.encryptor = :sha512 221 | 222 | # ==> Scopes configuration 223 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 224 | # "users/sessions/new". It's turned off by default because it's slower if you 225 | # are using only default views. 226 | # config.scoped_views = false 227 | 228 | # Configure the default scope given to Warden. By default it's the first 229 | # devise role declared in your routes (usually :user). 230 | # config.default_scope = :user 231 | 232 | # Set this configuration to false if you want /users/sign_out to sign out 233 | # only the current scope. By default, Devise signs out all scopes. 234 | # config.sign_out_all_scopes = true 235 | 236 | # ==> Navigation configuration 237 | # Lists the formats that should be treated as navigational. Formats like 238 | # :html, should redirect to the sign in page when the user does not have 239 | # access, but formats like :xml or :json, should return 401. 240 | # 241 | # If you have any extra navigational formats, like :iphone or :mobile, you 242 | # should add them to the navigational formats lists. 243 | # 244 | # The "*/*" below is required to match Internet Explorer requests. 245 | # config.navigational_formats = ['*/*', :html] 246 | 247 | # The default HTTP method used to sign out a resource. Default is :delete. 248 | config.sign_out_via = :delete 249 | 250 | # ==> OmniAuth 251 | # Add a new OmniAuth provider. Check the wiki for more information on setting 252 | # up on your models and hooks. 253 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' 254 | 255 | # ==> Warden configuration 256 | # If you want to use other strategies, that are not supported by Devise, or 257 | # change the failure app, you can configure them inside the config.warden block. 258 | # 259 | # config.warden do |manager| 260 | # manager.intercept_401 = false 261 | # manager.default_strategies(scope: :user).unshift :some_external_strategy 262 | # end 263 | 264 | # ==> Mountable engine configurations 265 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine 266 | # is mountable, there are some extra configurations to be taken into account. 267 | # The following options are available, assuming the engine is mounted as: 268 | # 269 | # mount MyEngine, at: '/my_engine' 270 | # 271 | # The router that invoked `devise_for`, in the example above, would be: 272 | # config.router_name = :my_engine 273 | # 274 | # When using OmniAuth, Devise cannot automatically set OmniAuth path, 275 | # so you need to do it manually. For the users scope, it would be: 276 | # config.omniauth_path_prefix = '/my_engine/users/auth' 277 | end 278 | --------------------------------------------------------------------------------