talking_stick 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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) %>