shove 0.52 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +17 -0
  3. data/Gemfile.lock +63 -0
  4. data/README.markdown +263 -0
  5. data/Rakefile +47 -0
  6. data/bin/shove +128 -106
  7. data/lib/shove/app.rb +78 -0
  8. data/lib/shove/app_directory.rb +104 -0
  9. data/lib/shove/client/callback.rb +24 -0
  10. data/lib/shove/client/channel.rb +81 -0
  11. data/lib/shove/client/connection.rb +167 -0
  12. data/lib/shove/http/channel_context.rb +60 -0
  13. data/lib/shove/http/client_context.rb +82 -0
  14. data/lib/shove/http/request.rb +94 -0
  15. data/lib/shove/http/response.rb +30 -0
  16. data/lib/shove/protocol.rb +53 -0
  17. data/lib/shove.rb +60 -78
  18. data/shove.gemspec +11 -20
  19. data/spec/app_directory_spec.rb +66 -0
  20. data/spec/cassettes/should_authorize_oneself.yml +24 -0
  21. data/spec/cassettes/should_be_able_to_authorize_with_the_server.yml +24 -0
  22. data/spec/cassettes/should_cancel_a_binding.yml +24 -0
  23. data/spec/cassettes/should_configure_the_default.yml +24 -0
  24. data/spec/cassettes/should_configure_the_from_the_previous_test.yml +24 -0
  25. data/spec/cassettes/should_create_a_channel_context.yml +24 -0
  26. data/spec/cassettes/should_deny_a_connection.yml +24 -0
  27. data/spec/cassettes/should_deny_a_control_to_a_client.yml +24 -0
  28. data/spec/cassettes/should_deny_a_publishing_to_a_client.yml +24 -0
  29. data/spec/cassettes/should_deny_a_subscriptions_to_a_client.yml +24 -0
  30. data/spec/cassettes/should_deny_publishing_on_a_channel_context.yml +24 -0
  31. data/spec/cassettes/should_deny_subscriptions_on_a_channel_context.yml +24 -0
  32. data/spec/cassettes/should_get_a_set_of_nodes_for_the_network.yml +24 -0
  33. data/spec/cassettes/should_get_a_subscribe_granted_event.yml +24 -0
  34. data/spec/cassettes/should_grant_a_connection.yml +24 -0
  35. data/spec/cassettes/should_grant_a_control_to_a_client.yml +24 -0
  36. data/spec/cassettes/should_grant_a_publishing_to_a_client.yml +24 -0
  37. data/spec/cassettes/should_grant_a_subscriptions_to_a_client.yml +24 -0
  38. data/spec/cassettes/should_grant_publishing_on_a_channel_context.yml +24 -0
  39. data/spec/cassettes/should_grant_subscriptions_on_a_channel_context.yml +24 -0
  40. data/spec/cassettes/should_publish.yml +24 -0
  41. data/spec/cassettes/should_publish_on_a_channel_context.yml +24 -0
  42. data/spec/cassettes/should_publish_to_a_client.yml +24 -0
  43. data/spec/cassettes/should_receive_an_unsubscribe_event.yml +24 -0
  44. data/spec/cassettes/should_receive_messages_on_a_channel.yml +24 -0
  45. data/spec/cassettes/should_send_a_connect_op.yml +24 -0
  46. data/spec/cassettes/should_send_a_connect_op_with_an_id.yml +24 -0
  47. data/spec/cassettes/should_spawn_a_client.yml +24 -0
  48. data/spec/cassettes/should_subscribe_to_a_channel.yml +24 -0
  49. data/spec/cassettes/should_trigger_a_connect_denied_event.yml +24 -0
  50. data/spec/cassettes/should_trigger_a_connect_event.yml +24 -0
  51. data/spec/cassettes/should_trigger_a_disconnect_event.yml +24 -0
  52. data/spec/cassettes/should_trigger_an_error_event.yml +24 -0
  53. data/spec/cassettes/should_unsubscribe_from_a_channel.yml +24 -0
  54. data/spec/cassettes/should_update_the_default_app.yml +24 -0
  55. data/spec/helper.rb +60 -0
  56. data/spec/shove_client_spec.rb +194 -0
  57. data/spec/shove_http_spec.rb +142 -0
  58. metadata +111 -81
  59. data/README.md +0 -71
  60. data/lib/shove/client.rb +0 -54
  61. data/lib/shove/request.rb +0 -80
  62. data/lib/shove/response.rb +0 -23
  63. data/spec/shove_spec.rb +0 -40
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ shove.yml
2
+ example.rb
3
+ Gemfile.lock
4
+ README.html
5
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ group :development do
5
+ gem "rake"
6
+ gem "redcarpet"
7
+ gem "albino"
8
+ end
9
+
10
+ group :test do
11
+ gem "rspec"
12
+ gem "vcr", "2.0.0.rc1"
13
+ gem "webmock"
14
+ gem "net-http-spy"
15
+ end
16
+
17
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shove (1.0.1)
5
+ confstruct (>= 0.2.1)
6
+ em-http-request (>= 1.0.0)
7
+ em-ws-client (>= 0.1.1)
8
+ yajl-ruby (>= 1.1.0)
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ addressable (2.2.6)
14
+ albino (1.3.3)
15
+ posix-spawn (>= 0.3.6)
16
+ confstruct (0.2.1)
17
+ cookiejar (0.3.0)
18
+ crack (0.3.1)
19
+ diff-lcs (1.1.3)
20
+ em-http-request (1.0.1)
21
+ addressable (>= 2.2.3)
22
+ cookiejar
23
+ em-socksify
24
+ eventmachine (>= 1.0.0.beta.4)
25
+ http_parser.rb (>= 0.5.3)
26
+ em-socksify (0.1.0)
27
+ eventmachine
28
+ em-ws-client (0.1.1)
29
+ eventmachine (~> 1.0.0.beta.3)
30
+ state_machine (~> 1.0.2)
31
+ eventmachine (1.0.0.beta.4)
32
+ http_parser.rb (0.5.3)
33
+ net-http-spy (0.2.1)
34
+ posix-spawn (0.3.6)
35
+ rake (0.9.2.2)
36
+ redcarpet (2.1.0)
37
+ rspec (2.8.0)
38
+ rspec-core (~> 2.8.0)
39
+ rspec-expectations (~> 2.8.0)
40
+ rspec-mocks (~> 2.8.0)
41
+ rspec-core (2.8.0)
42
+ rspec-expectations (2.8.0)
43
+ diff-lcs (~> 1.1.2)
44
+ rspec-mocks (2.8.0)
45
+ state_machine (1.0.3)
46
+ vcr (2.0.0.rc1)
47
+ webmock (1.7.10)
48
+ addressable (~> 2.2, > 2.2.5)
49
+ crack (>= 0.1.7)
50
+ yajl-ruby (1.1.0)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ albino
57
+ net-http-spy
58
+ rake
59
+ redcarpet
60
+ rspec
61
+ shove!
62
+ vcr (= 2.0.0.rc1)
63
+ webmock
data/README.markdown ADDED
@@ -0,0 +1,263 @@
1
+ shove ruby client
2
+ =================
3
+ Ruby client and CLI for the shove.io HTTP and WebSocket APIs
4
+
5
+ Installation
6
+ ------------
7
+
8
+ ```bash
9
+ gem install shove
10
+ ```
11
+
12
+ Grab your App ID and App Key from shove at [https://shove.io/apps][0]
13
+
14
+ Configuring Shove
15
+ -----------------
16
+
17
+ If you are using a single shove app within your project, you can configure
18
+ shove though the Shove module:
19
+
20
+ ```ruby
21
+ Shove.configure do
22
+ app_id "myappid"
23
+ app_key "myappkey"
24
+ end
25
+ ```
26
+
27
+ If you want to work with different shove apps in one project, you can
28
+ create App objects
29
+
30
+ ```ruby
31
+ app = Shove::App.new do
32
+ app_id "myappid"
33
+ app_key "myappkey"
34
+ end
35
+ ```
36
+
37
+ Using the HTTP Client
38
+ ---------------------
39
+ The HTTP client gives publishing and access control capabilities without
40
+ a persistent WebSocket connection
41
+
42
+ Publish "Hello World!" to all connections on the notifications channel
43
+
44
+ ```ruby
45
+ Shove.channel("notifications").publish("Hello World!") do |response|
46
+ # handle response
47
+ end
48
+ ```
49
+
50
+ Publish "Hey buddy" to the client with id buddy.
51
+
52
+ ```ruby
53
+ Shove.channel("direct:buddy").publish("Hey buddy") do |response|
54
+ # handle response
55
+ end
56
+ ```
57
+
58
+ Apps, channels, and clients can be controlled from the HTTP API
59
+
60
+ Grant a connection to dan@shove.io
61
+ ```ruby
62
+ Shove.grant_connect("dan@shove.io") do |response|
63
+ # handle response
64
+ end
65
+ ```
66
+
67
+ Grant subscription on the notifications channel to client dan
68
+
69
+ ```ruby
70
+ Shove.channel("notifications").grant_subscribe("dan") do |response|
71
+ # handle response
72
+ end
73
+ ```
74
+
75
+ Grant subscription on all channels to client dan
76
+
77
+ ```ruby
78
+ Shove.channel("*").grant_subscribe("dan@shove.io") do |response|
79
+ # handle response
80
+ end
81
+ ```
82
+
83
+ Grant publishing on chat:client_22733 channel to client dan
84
+
85
+ ```ruby
86
+ Shove.channel("chat:client_22733").grant_publish("dan") do |response|
87
+ # handle response
88
+ end
89
+ ```
90
+
91
+ Deny publishing on chat:client_22733 channel to dan
92
+
93
+ ```ruby
94
+ Shove.channel("chat:client_22733").deny_publish("dan") do |response|
95
+ # handle response
96
+ end
97
+ ```
98
+
99
+ Using the WebSocket Client
100
+ --------------------------
101
+
102
+ Create a WebSocket client on the default app
103
+
104
+ ```ruby
105
+ client = Shove.app.connect
106
+ ```
107
+
108
+ Or
109
+
110
+ ```ruby
111
+ app = Shove::App.new do
112
+ app_id "myappid"
113
+ end
114
+ client = app.connect
115
+ ```
116
+
117
+ Create a client with a custom ID
118
+
119
+ ```ruby
120
+ client = Shove.app.connect("unique_id")
121
+ ```
122
+
123
+ ### Client events
124
+
125
+ Handle connect event
126
+
127
+ ```ruby
128
+ client.on("connect") do
129
+ # Connected!
130
+ end
131
+ ```
132
+
133
+ Handle connect denies (private app)
134
+
135
+ ```ruby
136
+ client.on("connect_denied") do |id|
137
+ # Silly, but:
138
+ Shove.client(id).grant_connect do |response|
139
+ # At this point, the client should receive
140
+ # a connect event (through the shove app)
141
+ end
142
+ end
143
+ ```
144
+
145
+ And disconnect events
146
+
147
+ ```ruby
148
+ client.on("disconnect") do
149
+ end
150
+ ```
151
+
152
+ If there is any kind of error, log it
153
+
154
+ ```ruby
155
+ client.on("error") do |error|
156
+ log.error "Shove error: #{error}"
157
+ end
158
+ ```
159
+
160
+ ### Clients and Channels
161
+
162
+ Subscribe to a channel or get a subscribed channel
163
+
164
+ ```ruby
165
+ channel = client.channel("channel")
166
+ ```
167
+
168
+ Handle a message published on a channel
169
+
170
+ ```ruby
171
+ channel.on("message") do |msg|
172
+ widget.append msg
173
+ end
174
+ ```
175
+
176
+ Handle the subscribe callback for a given channel
177
+
178
+ ```ruby
179
+ channel.on("subscribe") do
180
+ # channel is subscribed
181
+ end
182
+ ```
183
+
184
+ If the app denies subscriptions by default, you should
185
+ handle the subscribe_denied event
186
+
187
+ ```ruby
188
+ channel.on("subscribe_denied") do
189
+ # Silly example
190
+ Shove.client(client.id).grant_subscribe(channel.name)
191
+ end
192
+ ```
193
+
194
+ You can get the binding for a callback and cancel it
195
+
196
+ ```ruby
197
+ binding = channel.on("message") do |msg|
198
+ end
199
+
200
+ binding.cancel
201
+ ```
202
+
203
+ Handle a direct message
204
+
205
+ ```ruby
206
+ client.channel("direct").on("message") do |msg|
207
+ # handle direct message
208
+ end
209
+ ```
210
+
211
+ Unsubscribe from a channel, optionally handle the event
212
+
213
+ ```ruby
214
+ channel.on("unsubscribe_complete") do
215
+ end
216
+
217
+ channel.unsubscribe
218
+ ```
219
+
220
+ Publish a message from the WebSocket client
221
+
222
+ ```ruby
223
+ channel.publish("hi!")
224
+
225
+ # publish json
226
+ channel.publish(obj.to_json)
227
+ ```
228
+
229
+ Using the Command Line
230
+ ----------------------
231
+
232
+ A CLI utility is included with the gem, run help to see options
233
+
234
+ ```bash
235
+ shove help
236
+ ```
237
+
238
+ Publish a message
239
+
240
+ ```bash
241
+ shove publish -c channel1 -m "Hello world!"
242
+ ```
243
+
244
+ Publish to a specific app
245
+
246
+ ```bash
247
+ shove publish -a app_id -c chan1 -m "hi"
248
+ ```
249
+
250
+ Set the default app for the CLI
251
+
252
+ ```bash
253
+ shove apps:default -a app_id
254
+ ```
255
+
256
+ Watch all activity on a channel
257
+
258
+ ```bash
259
+ shove watch -c chan
260
+ ```
261
+
262
+ [0]: https://shove.io/apps
263
+ [1]: http://shove.io/documentation/cli
data/Rakefile CHANGED
@@ -4,3 +4,50 @@ RSpec::Core::RakeTask.new(:spec)
4
4
 
5
5
  task :default => :spec
6
6
 
7
+ task :autospec => :spec do
8
+ require "eventmachine"
9
+
10
+ $time = Time.now
11
+
12
+ module Handler
13
+ def file_modified
14
+ if Time.now - $time > 1
15
+ $time = Time.now
16
+ Rake::Task["spec"].execute
17
+ end
18
+ end
19
+ end
20
+
21
+ EM.kqueue = true if EM.kqueue?
22
+ EM.run do
23
+ ["spec","lib"].collect { |dir|
24
+ Dir.glob(File.dirname(__FILE__) + "/#{dir}/**/*.rb")
25
+ }.flatten.each do |file|
26
+ EM.watch_file file, Handler
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ desc "Generate the README HTML"
33
+ task :readme do
34
+
35
+ require "redcarpet"
36
+ require "albino"
37
+
38
+ # Create a custom renderer that uses albino to
39
+ # make pretty code
40
+ class Colorizer < Redcarpet::Render::HTML
41
+ def block_code(code, language)
42
+ Albino.colorize(code, language)
43
+ end
44
+ end
45
+
46
+ content = Redcarpet::Markdown.new(Colorizer, :fenced_code_blocks => true)
47
+ .render(File.read("README.markdown"))
48
+
49
+ File.open("README.html", "w") do |f|
50
+ f << content
51
+ end
52
+
53
+ end
data/bin/shove CHANGED
@@ -1,133 +1,155 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rubygems"
4
- require "commander/import"
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+
5
+ require "optparse"
5
6
  require "shove"
6
7
 
7
- ymlpath = File.expand_path("~/.shove.yml")
8
-
9
- # get network, key, cluster
10
- unless FileTest.exist?(ymlpath)
11
-
12
- access = {}
13
-
14
- loop do
15
-
16
- say "Please enter your network information."
17
- say "Visit http://shove.io/customer/network/api_access for more info."
18
-
19
- access[:cluster] = ask "Enter Cluster Group: "
20
- access[:network] = ask "Enter Network Id: "
21
- access[:key] = ask "Enter Network Key: "
22
-
23
- Shove.configure access
24
-
25
- check = Shove.validate
26
- if check.status >= 400
27
- say "Unable to validate network settings. Error: #{check.message}"
28
- else
29
- break
30
- end
31
-
8
+ action = ARGV[0]
9
+ channel = nil
10
+ client = nil
11
+ message = nil
12
+ app = nil
13
+ app_dir = Shove::AppDirectory.new
14
+
15
+ OptionParser.new do |o|
16
+
17
+ o.banner = "Usage: shove "
18
+
19
+ o.on("-h", "--help") do
20
+ action = "help"
32
21
  end
33
22
 
34
- File.open(ymlpath, "w") do |f|
35
- f << access.to_yaml
23
+ o.on("-v", "--version") do
24
+ action = "version"
36
25
  end
37
-
38
- end
39
26
 
40
- Shove.configure(ymlpath)
27
+ o.on("-l", "--list") do
28
+ action = "apps"
29
+ end
41
30
 
42
- # Commander specification
31
+ o.on("-a", "--app app", "the app to work with") do |arg|
32
+ app = arg
33
+ end
43
34
 
44
- program :name, "shove"
45
- program :version, Shove::Version
46
- program :description, "CLI for the shove.io push platform"
47
- program :help, "Author", "Dan Simpson <dan@shove.io>"
48
- program :formatter, :compact
35
+ o.on("-c", "--channel channel", "the channel to operate on") do |arg|
36
+ channel = arg
37
+ end
49
38
 
50
- command :stream do |c|
51
- c.syntax = "shove stream channel"
52
- c.example "Streams the default channel", "shove stream default"
53
- c.description = "Stream all data on a given channel to the console"
54
- c.action do |args, opts|
55
- if args.empty?
56
- say_invalid c
57
- else
58
- EM.run do
59
- Shove.stream(args.first) { |m|
60
- puts m[:channel]
61
- puts m[:event]
62
- puts m[:to] # this will either be empty or self
63
- puts m[:from] # message sender
64
- puts m[:data] # the payload
65
- puts "============"
66
- }
67
- end
68
- end
39
+ o.on("-u", "--client client", "the client to operate on") do |arg|
40
+ client = arg
69
41
  end
70
- end
71
- alias_command :subscribe, :stream
72
-
73
- command :broadcast do |c|
74
- c.syntax = "shove broadcast channel event data"
75
- c.example "Broadcast x,y coordinates on the grid channel", "shove broadcast grid coords '1522,234'"
76
- c.description = "Broadcast event and data to all subscribers of a given channel"
77
- c.action do |args, opts|
78
- if args.length != 3
79
- say_invalid c
80
- else
81
- say "Broadcasting on channel #{args.first}"
82
- Shove.broadcast(*args) do |response|
83
- say response.message
84
- end
85
- end
42
+
43
+ o.on("-m", "--message message", "the message to publish") do |arg|
44
+ message = arg
86
45
  end
46
+
47
+ o.parse!(ARGV)
87
48
  end
88
49
 
89
- command :direct do |c|
90
- c.syntax = "shove direct user event data"
91
- c.example "Send a message to user with id 9812737890", "shove direct 9812737890 message 'Your account expires in 2 minutes'"
92
- c.description = "Send a message directly to a specific shove subscriber"
93
- c.action do |args, opts|
94
- if args.length != 3
95
- say_invalid c
50
+ begin
51
+
52
+ case action
53
+ when "version"
54
+ puts "shove version #{Shove::Version}"
55
+ when "apps"
56
+
57
+ apps = app_dir.apps
58
+
59
+ if apps.empty?
60
+ puts "No apps found. Run: shove apps:add"
96
61
  else
97
- say "Sending data directly to user #{args.first}"
98
- Shove.direct(*args) do |response|
99
- say response.message
62
+ puts "App Id\t\tApp Key"
63
+ puts "-----------------------"
64
+ apps.each_pair do |id,key|
65
+ puts "#{id}\t\t#{key}"
100
66
  end
101
67
  end
102
- end
103
- end
104
68
 
105
- command :authorize do |c|
106
- c.syntax = "shove authorize user channel"
107
- c.example "Authorize user 9812737890 access to channel internal-bus", "shove authorize 9812737890 internal-bus"
108
- c.description = "Authorize a pending channel subscription. Only required for private channels."
109
- c.action do |args, opts|
110
- if args.length != 2
111
- say_invalid c
112
- else
113
- say "Authorizing user #{args.first} on channel #{args.last}"
114
- Shove.authorize(*args) do |response|
115
- say response.message
69
+ when "apps:add"
70
+ app_dir.get_config app
71
+ when "apps:default"
72
+ app_dir.default = app
73
+ when "hosts"
74
+ Shove.configure(app_dir.get_config(app))
75
+
76
+ Shove.hosts.each do |host|
77
+ puts host
78
+ end
79
+
80
+ when "publish"
81
+
82
+ Shove.configure(app_dir.get_config(app))
83
+
84
+ errors = []
85
+ errors << "channel not defined" unless channel
86
+ errors << "message not defined" unless message
87
+
88
+ if errors.empty?
89
+ Shove.channel(channel).publish(message) do |r|
90
+ if r.error?
91
+ puts "Error: #{r.message}"
92
+ else
93
+ puts "Published"
94
+ end
116
95
  end
96
+ else
97
+ puts "Errors: #{errors.join(",")}"
117
98
  end
118
- end
119
- end
120
99
 
100
+ when "watch"
121
101
 
122
- def say_invalid cmd
123
- puts "Invalid #{cmd.name} command. Syntax: #{cmd.syntax}"
124
- unless cmd.examples.empty?
125
- puts "Examples"
126
- cmd.examples.each do |ex|
127
- puts "---------"
128
- puts ex.first
129
- puts ex.last
102
+ Shove.configure(app_dir.get_config(app))
103
+
104
+ if channel
105
+
106
+ EM.run do
107
+ client = Shove.connect
108
+ client.authorize(app_dir.key(app))
109
+
110
+ client.on("error") do |error|
111
+ puts "Watch Error #{error}"
112
+ EM.stop
113
+ end
114
+
115
+ channel = client.channel(channel)
116
+ channel.on("message") do |msg|
117
+ puts "#{Time.now}: #{msg}"
118
+ end
119
+ end
120
+
121
+ else
122
+ puts "You must provide the channel with: -c channel_name"
130
123
  end
124
+
125
+ else
126
+ puts File.read(__FILE__).split("__END__").last
131
127
  end
128
+
129
+
130
+ rescue Exception => ex
131
+
132
+ puts ex.message
133
+ puts ex.backtrace
134
+
135
+ abort File.read(__FILE__).split("__END__").last
132
136
  end
133
137
 
138
+ __END__
139
+ Usage: shove action [options]
140
+
141
+
142
+ Options
143
+ -h, --help
144
+ Show this message
145
+
146
+ Actions
147
+ publish publish a message to the app
148
+ example: shove publish -c chan -m "Hello"
149
+
150
+ hosts List available hosts for your app
151
+ watch Watch the messages on a given channel
152
+
153
+ apps List known apps
154
+ apps:default Use a particular app
155
+ apps:add Add a new app