shove 0.52 → 1.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 (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