talking_stick 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +15 -0
  5. data/LICENSE.md +50 -0
  6. data/README.md +111 -0
  7. data/Rakefile +32 -0
  8. data/app/assets/images/talking_stick/.keep +0 -0
  9. data/app/assets/javascripts/talking_stick/application.js +15 -0
  10. data/app/assets/javascripts/talking_stick/talking_stick.js +141 -0
  11. data/app/assets/javascripts/talking_stick/talking_stick/partner.js +100 -0
  12. data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +117 -0
  13. data/app/assets/stylesheets/talking_stick/application.css +15 -0
  14. data/app/controllers/talking_stick/application_controller.rb +4 -0
  15. data/app/controllers/talking_stick/participants_controller.rb +72 -0
  16. data/app/controllers/talking_stick/rooms_controller.rb +122 -0
  17. data/app/helpers/talking_stick/application_helper.rb +4 -0
  18. data/app/models/talking_stick/participant.rb +18 -0
  19. data/app/models/talking_stick/room.rb +6 -0
  20. data/app/models/talking_stick/signal.rb +17 -0
  21. data/app/views/layouts/talking_stick/application.html.erb +14 -0
  22. data/app/views/talking_stick/participants/_form.html.erb +25 -0
  23. data/app/views/talking_stick/participants/edit.html.erb +6 -0
  24. data/app/views/talking_stick/participants/index.html.erb +29 -0
  25. data/app/views/talking_stick/participants/new.html.erb +5 -0
  26. data/app/views/talking_stick/participants/show.html.erb +14 -0
  27. data/app/views/talking_stick/rooms/_form.html.erb +25 -0
  28. data/app/views/talking_stick/rooms/edit.html.erb +6 -0
  29. data/app/views/talking_stick/rooms/index.html.erb +29 -0
  30. data/app/views/talking_stick/rooms/new.html.erb +5 -0
  31. data/app/views/talking_stick/rooms/show.html.erb +40 -0
  32. data/bin/rails +12 -0
  33. data/config/routes.rb +7 -0
  34. data/db/migrate/20150510181337_create_talking_stick_rooms.rb +10 -0
  35. data/db/migrate/20150510182258_create_talking_stick_participants.rb +14 -0
  36. data/db/migrate/20150511005922_create_talking_stick_signals.rb +12 -0
  37. data/lib/talking_stick.rb +4 -0
  38. data/lib/talking_stick/engine.rb +12 -0
  39. data/lib/talking_stick/version.rb +3 -0
  40. data/lib/tasks/talking_stick_tasks.rake +4 -0
  41. data/spec/dummy/README.rdoc +28 -0
  42. data/spec/dummy/Rakefile +6 -0
  43. data/spec/dummy/app/assets/images/.keep +0 -0
  44. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  45. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  47. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  48. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  49. data/spec/dummy/app/mailers/.keep +0 -0
  50. data/spec/dummy/app/models/.keep +0 -0
  51. data/spec/dummy/app/models/concerns/.keep +0 -0
  52. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  53. data/spec/dummy/bin/bundle +3 -0
  54. data/spec/dummy/bin/rails +4 -0
  55. data/spec/dummy/bin/rake +4 -0
  56. data/spec/dummy/bin/setup +29 -0
  57. data/spec/dummy/config.ru +4 -0
  58. data/spec/dummy/config/application.rb +32 -0
  59. data/spec/dummy/config/boot.rb +5 -0
  60. data/spec/dummy/config/database.yml +25 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +41 -0
  63. data/spec/dummy/config/environments/production.rb +79 -0
  64. data/spec/dummy/config/environments/test.rb +42 -0
  65. data/spec/dummy/config/initializers/assets.rb +11 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/spec/dummy/config/initializers/inflections.rb +16 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +23 -0
  74. data/spec/dummy/config/routes.rb +4 -0
  75. data/spec/dummy/config/secrets.yml +22 -0
  76. data/spec/dummy/lib/assets/.keep +0 -0
  77. data/spec/dummy/log/.keep +0 -0
  78. data/spec/dummy/public/404.html +67 -0
  79. data/spec/dummy/public/422.html +67 -0
  80. data/spec/dummy/public/500.html +66 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/models/talking_stick/participant_spec.rb +7 -0
  83. data/spec/models/talking_stick/room_spec.rb +7 -0
  84. data/spec/spec_helper.rb +13 -0
  85. data/talking_stick.gemspec +27 -0
  86. data/vendor/assets/javascripts/talking_stick/adapter.js +243 -0
  87. metadata +258 -0
@@ -0,0 +1,117 @@
1
+ TalkingStick.RailsSignaling = function(options) {
2
+ this.options = options;
3
+ this.roomUrl = options.url;
4
+ }
5
+
6
+ TalkingStick.RailsSignaling.prototype.connected = function() {
7
+ // Check now, then schedule a timer to check for new participants
8
+ this._updateRoom();
9
+ var signaling = this;
10
+ setInterval(function() {
11
+ signaling._updateRoom.apply(signaling);
12
+ }, 3000);
13
+ }
14
+
15
+ TalkingStick.RailsSignaling.prototype.sendICECandidate = function(to, candidate) {
16
+ // Noop - wait for all candidates to be gathered and send at once
17
+ // This is slower for the user, but reduces traffic on the API
18
+ }
19
+
20
+ TalkingStick.RailsSignaling.prototype.iceCandidateGatheringComplete = function(to, candidates) {
21
+ var data = {
22
+ signal_type: 'candidates',
23
+ data: JSON.stringify(candidates),
24
+ }
25
+ this._sendData('ICE Candidates', to, data);
26
+ }
27
+
28
+ TalkingStick.RailsSignaling.prototype.sendOffer = function(to, offer) {
29
+ var data = {
30
+ signal_type: 'offer',
31
+ data: JSON.stringify(offer),
32
+ }
33
+ this._sendData('Offer', to, data);
34
+ }
35
+
36
+ TalkingStick.RailsSignaling.prototype.sendAnswer = function(to, answer) {
37
+ var data = {
38
+ signal_type: 'answer',
39
+ data: JSON.stringify(answer),
40
+ }
41
+ this._sendData('Answer', to, data);
42
+ }
43
+
44
+ TalkingStick.RailsSignaling.prototype._sendData = function(descr, toGuid, data) {
45
+ data.sender = TalkingStick.guid;
46
+ var signalUrl = this.roomUrl + '/participants/' + toGuid + '/signals';
47
+ $.post(signalUrl, data)
48
+ .success(function() { TalkingStick.log('trace', descr + ' sent to API'); })
49
+ .fail(function(jqXHR, textStatus, error) { TalkingStick.log('error', 'Error sending ' + descr + ' to API:', error) });
50
+ }
51
+
52
+ TalkingStick.RailsSignaling.prototype._updateRoom = function() {
53
+ TalkingStick.log('trace', 'Getting room updates');
54
+ var options = {
55
+ data: { guid: TalkingStick.guid },
56
+ }
57
+
58
+ var signaling = this;
59
+ $.ajax(this.roomUrl + '.json', options)
60
+ .success(function() { signaling._handleRoomUpdate.apply(signaling, arguments); })
61
+ .fail(function() { TalkingStick.ajaxErrorLog('Error checking for participants', arguments) });
62
+ };
63
+
64
+ TalkingStick.RailsSignaling.prototype._updateParticipants = function(participants) {
65
+ $.each(participants, function(i, participant) {
66
+ if (participant.guid === TalkingStick.guid) {
67
+ // Don't try to set up a connection to ourself
68
+ //TalkingStick.log('trace', 'Skipping own GUID', participant.guid);
69
+ return;
70
+ }
71
+
72
+ if (TalkingStick.partners[participant.guid]) {
73
+ // We already have a connection to this participant
74
+ //TalkingStick.log('trace', 'Skipping participant since we already have a connection', participant.guid);
75
+ return;
76
+ }
77
+ TalkingStick.log('trace', 'Handling new partner', participant.guid);
78
+
79
+ TalkingStick.addPartner(participant);
80
+ });
81
+ };
82
+
83
+ TalkingStick.RailsSignaling.prototype._processSignals = function(signals) {
84
+ $.each(signals, function(i, signal) {
85
+ TalkingStick.log('trace', 'Received signal', signal);
86
+ signal_data = JSON.parse(signal.data);
87
+
88
+ var partner = TalkingStick.partners[signal.sender_guid];
89
+ if (partner === undefined) {
90
+ TalkingStick.log('warning', 'Received signal from unknown partner ' + signal.sender_guid + '; ignoring.');
91
+ return;
92
+ }
93
+
94
+ switch(signal.signal_type) {
95
+ case 'offer':
96
+ partner.handleOffer(signal_data);
97
+ break;
98
+ case 'answer':
99
+ partner.handleAnswer(signal_data);
100
+ break;
101
+ case 'candidates':
102
+ $.each(signal_data, function(i, candidate) {
103
+ partner.handleRemoteICECandidate(candidate);
104
+ });
105
+ break;
106
+ default:
107
+ TalkingStick.log('warning', 'Unknown signal type "' + signal.signal_type + '" received.', signal);
108
+ break;
109
+ };
110
+ });
111
+ };
112
+
113
+ TalkingStick.RailsSignaling.prototype._handleRoomUpdate = function(data, textStatus, jqXHR) {
114
+ this._updateParticipants(data.participants);
115
+ this._processSignals(data.signals);
116
+ };
117
+
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module TalkingStick
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,72 @@
1
+ require_dependency "talking_stick/application_controller"
2
+
3
+ module TalkingStick
4
+ class ParticipantsController < ApplicationController
5
+ before_action :set_room
6
+ before_action :set_participant, only: [:show, :edit, :update, :destroy]
7
+
8
+ # GET /participants
9
+ def index
10
+ @participants = Participant.all
11
+ end
12
+
13
+ # GET /participants/1
14
+ def show
15
+ end
16
+
17
+ # GET /participants/new
18
+ def new
19
+ @participant = Participant.new
20
+ end
21
+
22
+ # GET /participants/1/edit
23
+ def edit
24
+ end
25
+
26
+ # POST /participants
27
+ def create
28
+ params = participant_params.merge ip: request.remote_ip
29
+ @participant = Participant.new(params)
30
+ @participant.room = @room
31
+
32
+ respond_to do |format|
33
+ if @participant.save
34
+ format.html { redirect_to [@room, @participant], notice: 'Participant was successfully created.' }
35
+ format.json { render json: @participant }
36
+ else
37
+ render :new
38
+ end
39
+ end
40
+ end
41
+
42
+ # PATCH/PUT /participants/1
43
+ def update
44
+ if @participant.update(participant_params)
45
+ redirect_to [@room, @participant], notice: 'Participant was successfully updated.'
46
+ else
47
+ render :edit
48
+ end
49
+ end
50
+
51
+ # DELETE /participants/1
52
+ def destroy
53
+ @participant.destroy
54
+ redirect_to participants_url, notice: 'Participant was successfully destroyed.'
55
+ end
56
+
57
+ private
58
+ def set_room
59
+ @room = Room.find params[:room_id]
60
+ end
61
+
62
+ # Use callbacks to share common setup or constraints between actions.
63
+ def set_participant
64
+ @participant = Participant.find params[:id]
65
+ end
66
+
67
+ # Only allow a trusted parameter "white list" through.
68
+ def participant_params
69
+ params.require(:participant).permit(:name, :ip, :guid)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,122 @@
1
+ require_dependency "talking_stick/application_controller"
2
+
3
+ module TalkingStick
4
+ class RoomsController < ApplicationController
5
+ before_action :set_room, only: [:show, :edit, :update, :destroy, :signal]
6
+ before_action :set_participant, only: [:signal]
7
+
8
+ # GET /rooms
9
+ def index
10
+ @rooms = Room.all
11
+ end
12
+
13
+ # GET /rooms/1
14
+ def show
15
+ if params[:guid]
16
+ if @participant = Participant.where(guid: params[:guid]).first
17
+ @participant.last_seen = Time.now
18
+ @participant.save
19
+ end
20
+ end
21
+
22
+ Participant.remove_stale! @room
23
+
24
+ response = {
25
+ room: @room,
26
+ participants: @room.participants,
27
+ signals: deliver_signals!,
28
+ }
29
+
30
+ respond_to do |format|
31
+ format.html
32
+ format.json { render json: response }
33
+ end
34
+ end
35
+
36
+ # GET /rooms/new
37
+ def new
38
+ @room = Room.new
39
+ end
40
+
41
+ # GET /rooms/1/edit
42
+ def edit
43
+ end
44
+
45
+ # POST /rooms
46
+ def create
47
+ @room = Room.new(room_params)
48
+
49
+ if @room.save
50
+ redirect_to @room, notice: 'Room was successfully created.'
51
+ else
52
+ render :new
53
+ end
54
+ end
55
+
56
+ # PATCH/PUT /rooms/1
57
+ def update
58
+ if @room.update(room_params)
59
+ redirect_to @room, notice: 'Room was successfully updated.'
60
+ else
61
+ render :edit
62
+ end
63
+ end
64
+
65
+ # DELETE /rooms/1
66
+ def destroy
67
+ @room.destroy
68
+ redirect_to rooms_url, notice: 'Room was successfully destroyed.'
69
+ end
70
+
71
+ def signal
72
+ signal = signal_params
73
+ signal[:room] = @room
74
+ signal[:sender] = Participant.where(guid: signal[:sender]).first
75
+ signal[:recipient] = @participant
76
+ TalkingStick::Signal.create! signal
77
+ head 204
78
+ end
79
+
80
+ def deliver_signals!
81
+ data = TalkingStick::Signal.where recipient: @participant
82
+
83
+ # Destroy the signals as we return them, since they have been delivered
84
+ result = []
85
+ data.each do |signal|
86
+ result << {
87
+ signal_type: signal.signal_type,
88
+ sender_guid: signal.sender_guid,
89
+ recipient_guid: signal.recipient_guid,
90
+ data: signal.data,
91
+ room_id: signal.room_id,
92
+ timestamp: signal.created_at,
93
+ }
94
+ end
95
+ data.delete_all
96
+ result
97
+ end
98
+
99
+ private
100
+ # Use callbacks to share common setup or constraints between actions.
101
+ def set_room
102
+ @room = Room.find(params[:id] || params[:room_id])
103
+ end
104
+
105
+ def set_participant
106
+ @participant = Participant.find(params[:participant_id])
107
+ rescue ActiveRecord::RecordNotFound
108
+ # Retry with ID as GUID
109
+ @participant = Participant.where(guid: params[:participant_id]).first
110
+ raise unless @participant
111
+ end
112
+
113
+ # Only allow a trusted parameter "white list" through.
114
+ def room_params
115
+ params.require(:room).permit(:name, :last_used)
116
+ end
117
+
118
+ def signal_params
119
+ params.permit(:sender, :signal_type, :data)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,4 @@
1
+ module TalkingStick
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,18 @@
1
+ module TalkingStick
2
+ class Participant < ActiveRecord::Base
3
+ belongs_to :room
4
+ has_many :signals_sent, class_name: 'Signal', foreign_key: 'sender_id', dependent: :destroy
5
+ has_many :signals_received, class_name: 'Signal', foreign_key: 'recipient_id', dependent: :destroy
6
+ before_save :set_defaults
7
+
8
+ def set_defaults
9
+ self.joined_at ||= Time.now
10
+ end
11
+
12
+ class << self
13
+ def remove_stale!(room)
14
+ self.where(room: room).where('last_seen < ?', Time.now - 5.minutes).delete_all
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module TalkingStick
2
+ class Room < ActiveRecord::Base
3
+ has_many :participants, dependent: :destroy
4
+ has_many :signals, dependent: :destroy
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module TalkingStick
2
+ class Signal < ActiveRecord::Base
3
+ belongs_to :room
4
+ belongs_to :sender, class_name: "TalkingStick::Participant"
5
+ belongs_to :recipient, class_name: "TalkingStick::Participant"
6
+ validates :room, :sender, :recipient, presence: true
7
+
8
+ # The normal delegate method seems to not be working for an unknown reason
9
+ def sender_guid
10
+ self.sender.guid
11
+ end
12
+
13
+ def recipient_guid
14
+ self.recipient.guid
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TalkingStick</title>
5
+ <%= stylesheet_link_tag "talking_stick/application", media: "all" %>
6
+ <%= javascript_include_tag "talking_stick/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,25 @@
1
+ <%= form_for([@room, @participant]) do |f| %>
2
+ <% if @participant.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(@participant.errors.count, "error") %> prohibited this participant from being saved:</h2>
5
+
6
+ <ul>
7
+ <% @participant.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="field">
15
+ <%= f.label :name %><br>
16
+ <%= f.text_field :name %>
17
+ </div>
18
+ <div class="field">
19
+ <%= f.label :ip %><br>
20
+ <%= f.text_field :ip %>
21
+ </div>
22
+ <div class="actions">
23
+ <%= f.submit %>
24
+ </div>
25
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <h1>Editing Participant</h1>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= link_to 'Show', [@room, @participant] %> |
6
+ <%= link_to 'Back', room_participants_path %>
@@ -0,0 +1,29 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <h1>Listing Participants</h1>
4
+
5
+ <table>
6
+ <thead>
7
+ <tr>
8
+ <th>Name</th>
9
+ <th>Ip</th>
10
+ <th colspan="3"></th>
11
+ </tr>
12
+ </thead>
13
+
14
+ <tbody>
15
+ <% @participants.each do |participant| %>
16
+ <tr>
17
+ <td><%= participant.name %></td>
18
+ <td><%= participant.ip %></td>
19
+ <td><%= link_to 'Show', [@room, participant] %></td>
20
+ <td><%= link_to 'Edit', edit_room_participant_path(@room, participant) %></td>
21
+ <td><%= link_to 'Destroy', [@room, participant], method: :delete, data: { confirm: 'Are you sure?' } %></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+
27
+ <br>
28
+
29
+ <%= link_to 'New Participant', new_room_participant_path(@room) %>