rest-firebase 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +4 -0
- data/Rakefile +1 -1
- data/doc/intro.md +150 -0
- data/lib/rest-firebase.rb +10 -4
- data/rest-firebase.gemspec +4 -3
- data/test/test_api.rb +8 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38ac75dc25a2212b5493f30523502a0c524e5423
|
4
|
+
data.tar.gz: 194f4cafa79ce00c6d33dd9dbdf086ce0c30bda3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e9dfc59ecf61bcdd560d72bfe851464f3a183bc514a98b1c5464d58f013cac9d2de2404be278005ed33f821f5becfd4a38984187fbfa83de96d95619ed04a53
|
7
|
+
data.tar.gz: 502858f091acd477c0e6ad2ed4c142845899ddaa8b16686ef2df5ff4e2134966bf7a54d3fd8c3e43b16a7f7bbfbc6c039a57d18d39ec66e235e01634195ab9e2
|
data/CHANGES.md
CHANGED
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ $LOAD_PATH.unshift(File.expand_path("#{dir}/rest-core/lib"))
|
|
10
10
|
|
11
11
|
Gemgem.init(dir) do |s|
|
12
12
|
s.name = 'rest-firebase'
|
13
|
-
s.version = '0.9.
|
13
|
+
s.version = '0.9.1'
|
14
14
|
s.homepage = 'https://github.com/CodementorIO/rest-firebase'
|
15
15
|
|
16
16
|
s.authors = ['Codementor', 'Lin Jen-Shin (godfat)']
|
data/doc/intro.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
# Codementor introduces you a new Firebase client for Ruby
|
3
|
+
|
4
|
+
## Why we pick Firebase
|
5
|
+
|
6
|
+
Here at Codementor we implemented all the realtime facilities with
|
7
|
+
[Firebase][], which is a great tool and service for realtime communication,
|
8
|
+
especially for their JavaScript library which could handle all those edge
|
9
|
+
cases like whenever the clients disconnected unexpectedly, how we could
|
10
|
+
process the data offline and when we have a chance to reconnect, reconnect
|
11
|
+
and resend the offline data, etc, which are definitely common enough and we
|
12
|
+
shall not ignore them.
|
13
|
+
|
14
|
+
[Firebase]: https://www.firebase.com/
|
15
|
+
|
16
|
+
## Why we need a Firebase client for Ruby
|
17
|
+
|
18
|
+
However, our server is written in Ruby, and we definitely need someway to let
|
19
|
+
the server communicate with the clients (browsers). For example, whenever
|
20
|
+
we want to programmatically broadcast some messages to certain users, it
|
21
|
+
would be much easier to do this from the server. Picking a Firebase client
|
22
|
+
for Ruby would be the most straightforward choice.
|
23
|
+
|
24
|
+
## Existing Firebase client for Ruby did not fit our need
|
25
|
+
|
26
|
+
Unfortunately, eventually we realized that the existing Firebase client for
|
27
|
+
Ruby, namely [firebase-ruby][], did not fit our need. The main reason is that
|
28
|
+
it did not support the [streaming feature from Firebase][streaming], which is
|
29
|
+
extremely important whenever we want the clients periodically notify the
|
30
|
+
server, (e.g. online presence) since the server needs to know the status in
|
31
|
+
order to do some other stuffs underneath in realtime. We could probably
|
32
|
+
implement this on our server, but why not just use Firebase whenever it's
|
33
|
+
already implemented, and we're using it?
|
34
|
+
|
35
|
+
[firebase-ruby]: https://github.com/oscardelben/firebase-ruby
|
36
|
+
[streaming]: https://www.firebase.com/docs/rest-api.html#streaming-from-the-rest-api
|
37
|
+
|
38
|
+
## [rest-firebase][]
|
39
|
+
|
40
|
+
Therefore we implemented our own Firebase client for Ruby, that is
|
41
|
+
[rest-firebase][]. It was built on top of [rest-core][], thus it has all
|
42
|
+
the advantages from rest-core, just like firebase-ruby was built on top
|
43
|
+
of [typhoeus][]. The highlights for rest-firebase are:
|
44
|
+
|
45
|
+
* Concurrent/asynchronous requests
|
46
|
+
* Streaming requests
|
47
|
+
* Generate Firebase JWT for you (auto-refresh is WIP)
|
48
|
+
|
49
|
+
[rest-firebase]: https://github.com/CodementorIO/rest-firebase
|
50
|
+
[rest-core]: https://github.com/godfat/rest-core
|
51
|
+
[typhoeus]: https://github.com/typhoeus/typhoeus
|
52
|
+
|
53
|
+
### Concurrent/asynchronous requests
|
54
|
+
|
55
|
+
At times we want to notify two users at the same time, instead of preparing
|
56
|
+
two requests and wait for two requests to be done, we could simply do this:
|
57
|
+
(not a working example, just try to demonstrate, see [README.md][] for
|
58
|
+
working example)
|
59
|
+
|
60
|
+
``` ruby
|
61
|
+
f = RestFirebase.new
|
62
|
+
f.put("users/#{a.id}", :message => 'Hi')
|
63
|
+
f.put("users/#{b.id}", :message => 'Oh')
|
64
|
+
```
|
65
|
+
|
66
|
+
All requests are non-blocking, and it would only block when we try to look at
|
67
|
+
the response. Therefore the above requests would be processed concurrently and
|
68
|
+
asynchronously. To learn more about this, check [Concurrent HTTP Requests][].
|
69
|
+
|
70
|
+
Also, consequently, if you're not waiting for the requests to be done
|
71
|
+
somewhere, you might want to wait `at_exit` to make sure all
|
72
|
+
requests are properly done like this:
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
at_exit do
|
76
|
+
RestFirebase.shutdown
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
Which would also shutdown the [thread pool][] if you're using it.
|
81
|
+
|
82
|
+
[README.md]: https://github.com/CodementorIO/rest-firebase/blob/master/README.md
|
83
|
+
[Concurrent HTTP Requests]: https://github.com/CodementorIO/rest-firebase/blob/master/README.md#concurrent-http-requests
|
84
|
+
[thread pool]: https://github.com/godfat/rest-core#thread-pool--connection-pool
|
85
|
+
|
86
|
+
### Streaming requests
|
87
|
+
|
88
|
+
To receive the online presence events, we have a specialized daemon to listen
|
89
|
+
on the presence node from Firebase. Something like below:
|
90
|
+
|
91
|
+
``` ruby
|
92
|
+
es = RestFirebase.new.event_source('presence')
|
93
|
+
es.onerror do |error|
|
94
|
+
Codementor.handle_error(error) unless error.kind_of?(EOFError)
|
95
|
+
end
|
96
|
+
|
97
|
+
es.onreconnect do
|
98
|
+
firebase.auth = nil # refresh auth
|
99
|
+
!!@start # don't reconnect if we're closing
|
100
|
+
end
|
101
|
+
|
102
|
+
es.onmessage do |event, data|
|
103
|
+
next unless event == 'put'
|
104
|
+
next unless username = data['path'][%r{^/(\w+)/web$}, 1]
|
105
|
+
onpresence(username, data['data'])
|
106
|
+
end
|
107
|
+
|
108
|
+
es.start
|
109
|
+
sleep(1) while @start
|
110
|
+
|
111
|
+
es.close
|
112
|
+
```
|
113
|
+
|
114
|
+
`onpresence` is the one doing our business logic.
|
115
|
+
|
116
|
+
### Generate Firebase JWT for you (auto-refresh is WIP)
|
117
|
+
|
118
|
+
We could use Firebase JWT instead of our secret in order to make authorized
|
119
|
+
requests. This would be much secure than simply use the secret, which would
|
120
|
+
never expire unless we explicitly ask for. Checkout
|
121
|
+
[Authenticating Your Server][] for more detail. [rest-firebase][] could
|
122
|
+
generate one for you automatically by passing your secret to it like this:
|
123
|
+
|
124
|
+
``` ruby
|
125
|
+
f = RestFirebase.new :secret => 'secret',
|
126
|
+
:d => {:auth_data => 'something'}
|
127
|
+
f.get('presence') # => attach JWT for auth in the request automatically
|
128
|
+
f.auth # => the JWT
|
129
|
+
f.auth = nil # => remove old JWT
|
130
|
+
f.auth # => generate a fresh new JWT
|
131
|
+
```
|
132
|
+
|
133
|
+
Read the above document for what `:d` means here. Note that this JWT
|
134
|
+
would expire after 24 hours. Every time you initialize a new `RestFirebase`
|
135
|
+
it would generate a fresh new JWT, but if you want to keep using the same
|
136
|
+
instance, you would probably need to refresh the JWT by yourselves, just like
|
137
|
+
what we did when we tried to reconnect it in the streaming example.
|
138
|
+
|
139
|
+
[Authenticating Your Server]: https://www.firebase.com/docs/security/custom-login.html#authenticating-your-server
|
140
|
+
|
141
|
+
## Summary
|
142
|
+
|
143
|
+
In order to take the full advantage of using Firebase with Ruby, we introduce
|
144
|
+
you [rest-firebase][], which highlights:
|
145
|
+
|
146
|
+
* Concurrent/asynchronous requests
|
147
|
+
* Streaming requests
|
148
|
+
* Generate Firebase JWT for you (auto-refresh is WIP)
|
149
|
+
|
150
|
+
Please feel free to try it and use it. It's released under Apache License 2.0.
|
data/lib/rest-firebase.rb
CHANGED
@@ -7,7 +7,8 @@ RestFirebase = RC::Builder.client(:d, :secret, :auth) do
|
|
7
7
|
use RC::Timeout , 10
|
8
8
|
|
9
9
|
use RC::DefaultSite , 'https://SampleChat.firebaseIO-demo.com/'
|
10
|
-
use RC::DefaultHeaders, {'Accept' => 'application/json'
|
10
|
+
use RC::DefaultHeaders, {'Accept' => 'application/json',
|
11
|
+
'Content-Type' => 'application/json'}
|
11
12
|
use RC::DefaultQuery , nil
|
12
13
|
|
13
14
|
use RC::FollowRedirect, 1
|
@@ -74,9 +75,14 @@ module RestFirebase::Client
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def request env, app=app
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
path = "#{env[REQUEST_PATH]}.json"
|
79
|
+
payload = if env[REQUEST_PAYLOAD]
|
80
|
+
{REQUEST_PAYLOAD => Json.encode(env[REQUEST_PAYLOAD])}
|
81
|
+
else
|
82
|
+
{}
|
83
|
+
end
|
84
|
+
|
85
|
+
super(env.merge(REQUEST_PATH => path).merge(payload), app)
|
80
86
|
end
|
81
87
|
|
82
88
|
def generate_auth opts={}
|
data/rest-firebase.gemspec
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: rest-firebase 0.9.
|
2
|
+
# stub: rest-firebase 0.9.1 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "rest-firebase"
|
6
|
-
s.version = "0.9.
|
6
|
+
s.version = "0.9.1"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = [
|
11
11
|
"Codementor",
|
12
12
|
"Lin Jen-Shin (godfat)"]
|
13
|
-
s.date = "2014-
|
13
|
+
s.date = "2014-06-28"
|
14
14
|
s.description = "Ruby Firebase REST API client built on top of [rest-core][].\n\n[rest-core]: https://github.com/godfat/rest-core"
|
15
15
|
s.email = ["help@codementor.io"]
|
16
16
|
s.files = [
|
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
"README.md",
|
24
24
|
"Rakefile",
|
25
25
|
"TODO.md",
|
26
|
+
"doc/intro.md",
|
26
27
|
"lib/rest-firebase.rb",
|
27
28
|
"rest-firebase.gemspec",
|
28
29
|
"task/README.md",
|
data/test/test_api.rb
CHANGED
@@ -14,6 +14,9 @@ describe RestFirebase do
|
|
14
14
|
|
15
15
|
path = 'https://a.json?auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9%0A.eyJ2IjowLCJpYXQiOjAsImQiOm51bGx9%0A.C9JtzZhiCrsClNdAQcE7Irngr2BZJCH4x1p-IHxfrAo%3D%0A'
|
16
16
|
|
17
|
+
json = '{"status":"ok"}'
|
18
|
+
rbon = {'status' => 'ok'}
|
19
|
+
|
17
20
|
def firebase
|
18
21
|
RestFirebase.new(:secret => 'nnf')
|
19
22
|
end
|
@@ -24,12 +27,15 @@ describe RestFirebase do
|
|
24
27
|
end
|
25
28
|
|
26
29
|
should 'put {"status":"ok"}' do
|
27
|
-
json = '{"status":"ok"}'
|
28
|
-
rbon = {'status' => 'ok'}
|
29
30
|
stub_request(:put, path).with(:body => json).to_return(:body => json)
|
30
31
|
firebase.put('https://a', rbon).should.eq rbon
|
31
32
|
end
|
32
33
|
|
34
|
+
should 'have no payload for delete' do
|
35
|
+
stub_request(:delete, path).with(:body => nil).to_return(:body => json)
|
36
|
+
firebase.delete('https://a').should.eq rbon
|
37
|
+
end
|
38
|
+
|
33
39
|
should 'parse event source' do
|
34
40
|
stub_request(:get, path).to_return(:body => <<-SSE)
|
35
41
|
event: put
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest-firebase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Codementor
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-06-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-core
|
@@ -44,6 +44,7 @@ files:
|
|
44
44
|
- README.md
|
45
45
|
- Rakefile
|
46
46
|
- TODO.md
|
47
|
+
- doc/intro.md
|
47
48
|
- lib/rest-firebase.rb
|
48
49
|
- rest-firebase.gemspec
|
49
50
|
- task/README.md
|