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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/LICENSE.md +50 -0
- data/README.md +111 -0
- data/Rakefile +32 -0
- data/app/assets/images/talking_stick/.keep +0 -0
- data/app/assets/javascripts/talking_stick/application.js +15 -0
- data/app/assets/javascripts/talking_stick/talking_stick.js +141 -0
- data/app/assets/javascripts/talking_stick/talking_stick/partner.js +100 -0
- data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +117 -0
- data/app/assets/stylesheets/talking_stick/application.css +15 -0
- data/app/controllers/talking_stick/application_controller.rb +4 -0
- data/app/controllers/talking_stick/participants_controller.rb +72 -0
- data/app/controllers/talking_stick/rooms_controller.rb +122 -0
- data/app/helpers/talking_stick/application_helper.rb +4 -0
- data/app/models/talking_stick/participant.rb +18 -0
- data/app/models/talking_stick/room.rb +6 -0
- data/app/models/talking_stick/signal.rb +17 -0
- data/app/views/layouts/talking_stick/application.html.erb +14 -0
- data/app/views/talking_stick/participants/_form.html.erb +25 -0
- data/app/views/talking_stick/participants/edit.html.erb +6 -0
- data/app/views/talking_stick/participants/index.html.erb +29 -0
- data/app/views/talking_stick/participants/new.html.erb +5 -0
- data/app/views/talking_stick/participants/show.html.erb +14 -0
- data/app/views/talking_stick/rooms/_form.html.erb +25 -0
- data/app/views/talking_stick/rooms/edit.html.erb +6 -0
- data/app/views/talking_stick/rooms/index.html.erb +29 -0
- data/app/views/talking_stick/rooms/new.html.erb +5 -0
- data/app/views/talking_stick/rooms/show.html.erb +40 -0
- data/bin/rails +12 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20150510181337_create_talking_stick_rooms.rb +10 -0
- data/db/migrate/20150510182258_create_talking_stick_participants.rb +14 -0
- data/db/migrate/20150511005922_create_talking_stick_signals.rb +12 -0
- data/lib/talking_stick.rb +4 -0
- data/lib/talking_stick/engine.rb +12 -0
- data/lib/talking_stick/version.rb +3 -0
- data/lib/tasks/talking_stick_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/models/talking_stick/participant_spec.rb +7 -0
- data/spec/models/talking_stick/room_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- data/talking_stick.gemspec +27 -0
- data/vendor/assets/javascripts/talking_stick/adapter.js +243 -0
- 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,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,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,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,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) %>
|