stackmob 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +175 -0
- data/README.md +210 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/lib/stackmob.rb +67 -0
- data/lib/stackmob/client.rb +90 -0
- data/lib/stackmob/data_store.rb +74 -0
- data/lib/stackmob/deployer.rb +37 -0
- data/lib/stackmob/helpers.rb +63 -0
- data/lib/stackmob/push.rb +63 -0
- data/lib/stackmob/rack/simple_oauth_provider.rb +59 -0
- data/lib/stackmob/rails.rb +15 -0
- data/lib/stackmob/railtie.rb +38 -0
- data/lib/stackmob/sinatra.rb +5 -0
- data/lib/stackmob/tasks.rb +40 -0
- data/stackmob.gemspec +99 -0
- data/test/helper.rb +22 -0
- data/test/integration/test_client.rb +45 -0
- data/test/integration/test_data_store.rb +31 -0
- data/test/integration/test_deployer.rb +23 -0
- data/test/integration/test_push.rb +24 -0
- data/test/integration_helper.rb +46 -0
- data/test/unit/stackmob/rack/test_simple_oauth_provider.rb +52 -0
- data/test/unit/stackmob/test_client.rb +105 -0
- data/test/unit/stackmob/test_data_store.rb +83 -0
- data/test/unit/stackmob/test_deployer.rb +21 -0
- data/test/unit/stackmob/test_push.rb +133 -0
- metadata +258 -0
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "stackmob"
|
18
|
+
gem.homepage = "http://github.com/stackmob/stackmob-ruby"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "Support Gem for StackMob Heroku Add-On"
|
21
|
+
gem.description = "Support Gem for StackMob Heroku Add-On"
|
22
|
+
gem.email = "jordan@stackmob.com"
|
23
|
+
gem.authors = ["StackMob"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
namespace :test do
|
31
|
+
|
32
|
+
|
33
|
+
Rake::TestTask.new(:unit) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/unit/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::TestTask.new(:integration) do |test|
|
40
|
+
test.libs << 'lib' << 'test'
|
41
|
+
test.pattern = 'test/integration/**/test_*.rb'
|
42
|
+
test.verbose = true
|
43
|
+
end
|
44
|
+
|
45
|
+
Rcov::RcovTask.new do |test|
|
46
|
+
test.libs << 'test'
|
47
|
+
test.pattern = 'test/**/test_*.rb'
|
48
|
+
test.verbose = true
|
49
|
+
test.rcov_opts << '--exclude "gems/*"'
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Run Unit & Integration Tests"
|
53
|
+
task :all => [:unit, :integration]
|
54
|
+
end
|
55
|
+
|
56
|
+
task :default => "test:all"
|
57
|
+
|
58
|
+
require 'yard'
|
59
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/stackmob.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'oauth'
|
16
|
+
require 'yajl'
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
require 'stackmob/client'
|
20
|
+
require 'stackmob/data_store'
|
21
|
+
require 'stackmob/push'
|
22
|
+
require 'stackmob/rack/simple_oauth_provider'
|
23
|
+
require 'stackmob/helpers'
|
24
|
+
|
25
|
+
module StackMob
|
26
|
+
|
27
|
+
SANDBOX = 0
|
28
|
+
PRODUCTION = 1
|
29
|
+
|
30
|
+
def self.config
|
31
|
+
@config ||= YAML.load_file("config/stackmob.yml")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.secret
|
35
|
+
StackMob.config[sm_env_str]['secret']
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.key
|
39
|
+
StackMob.config[sm_env_str]['key']
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.app_name
|
43
|
+
StackMob.config['sm_app_name']
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.client_name
|
47
|
+
StackMob.config['sm_client_name']
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.dev_url
|
51
|
+
"http://#{StackMob.client_name}.mob2.stackmob.com"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.env
|
55
|
+
(is_production?) ? PRODUCTION : SANDBOX
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.sm_env_str
|
59
|
+
env == PRODUCTION ? "production" : "development"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.is_production?
|
63
|
+
ENV["RACK_ENV"] == "production"
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'oauth'
|
16
|
+
require 'yajl'
|
17
|
+
|
18
|
+
module StackMob
|
19
|
+
class Client
|
20
|
+
|
21
|
+
VALID_METHODS = [:get, :post, :put, :delete]
|
22
|
+
|
23
|
+
class InvalidRequestMethod < ArgumentError; end # invalid HTTP Method/Verb is passed to the client
|
24
|
+
class RequestError < RuntimeError; end # response with code other than 200 received
|
25
|
+
class BadResponseBody < RuntimeError; end # Client receives response with invalid JSON
|
26
|
+
|
27
|
+
attr_accessor :app_name, :app_vsn
|
28
|
+
|
29
|
+
def initialize(base_url, app_name, app_vsn, oauth_key, oauth_secret)
|
30
|
+
self.app_name = app_name
|
31
|
+
self.app_vsn = app_vsn
|
32
|
+
|
33
|
+
create_oauth_client(oauth_key, oauth_secret, base_url)
|
34
|
+
end
|
35
|
+
|
36
|
+
def request(method, service, path, params = {})
|
37
|
+
request_path, request_body = generate_path_and_body(method, service, path, params)
|
38
|
+
|
39
|
+
response = @oauth_client.send(method, request_path, request_body)
|
40
|
+
|
41
|
+
rcode = response.code.to_i
|
42
|
+
if rcode >= 200 && rcode <= 299
|
43
|
+
parse_response(response) if method != :delete
|
44
|
+
else
|
45
|
+
raise RequestError.new("\nReq Method: #{method}\nReq. Path: #{request_path}\nReq. Body: #{request_body}\nResp. Code: #{rcode}, Resp Body: #{response.respond_to?(:body) ? response.body : 'unknown'}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_oauth_client(key, secret, url)
|
50
|
+
@oauth_client = OAuth::AccessToken.new(OAuth::Consumer.new(key, secret, :site => url))
|
51
|
+
end
|
52
|
+
private :create_oauth_client
|
53
|
+
|
54
|
+
def generate_path_and_body(method, service, path, params)
|
55
|
+
intermediate_path = full_path(service, path)
|
56
|
+
case method
|
57
|
+
when :get, :delete
|
58
|
+
[intermediate_path + "?" + params_to_qs(params), ""]
|
59
|
+
when :post, :put
|
60
|
+
[intermediate_path, Yajl::Encoder.encode(params)]
|
61
|
+
else
|
62
|
+
raise InvalidRequestMethod
|
63
|
+
end
|
64
|
+
end
|
65
|
+
private :generate_path_and_body
|
66
|
+
|
67
|
+
def params_to_qs(params)
|
68
|
+
params.to_a.map { |pair| pair.join("=") }.join("&")
|
69
|
+
end
|
70
|
+
private :params_to_qs
|
71
|
+
|
72
|
+
def parse_response(r)
|
73
|
+
Yajl::Parser.parse(r.body)
|
74
|
+
rescue Yajl::ParseError
|
75
|
+
raise BadResponseBody.new("#{r.body} is not valid JSON")
|
76
|
+
end
|
77
|
+
private :parse_response
|
78
|
+
|
79
|
+
def full_path(service, requested_path)
|
80
|
+
"/#{service}/#{self.app_vsn}/#{self.app_name}/#{strip_prepending_slash(requested_path)}"
|
81
|
+
end
|
82
|
+
private :full_path
|
83
|
+
|
84
|
+
def strip_prepending_slash(path)
|
85
|
+
path.gsub(/^\//, "")
|
86
|
+
end
|
87
|
+
private :strip_prepending_slash
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
class DataStore
|
17
|
+
|
18
|
+
attr_accessor :client
|
19
|
+
|
20
|
+
API_SVC = :api
|
21
|
+
|
22
|
+
def initialize(cl)
|
23
|
+
self.client = cl
|
24
|
+
end
|
25
|
+
|
26
|
+
def api_schema
|
27
|
+
self.client.request(:get, API_SVC, "/listapi")
|
28
|
+
end
|
29
|
+
|
30
|
+
def create(obj_name, params)
|
31
|
+
create!(obj_name, params)
|
32
|
+
rescue Client::RequestError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def create!(obj_name, params)
|
37
|
+
self.client.request(:post, API_SVC, obj_name_to_path(obj_name), params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(obj_name, params)
|
41
|
+
delete!(obj_name, params); true
|
42
|
+
rescue Client::RequestError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete!(obj_name, params)
|
47
|
+
self.client.request(:delete, API_SVC, obj_name_to_path(obj_name), params)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(obj_name, params = {})
|
51
|
+
self.client.request(:get, API_SVC, obj_name_to_path(obj_name), params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_one(obj_name, params)
|
55
|
+
get(obj_name, params).first
|
56
|
+
end
|
57
|
+
|
58
|
+
def update(obj_name, obj_id, params)
|
59
|
+
update!(obj_name, obj_id, params); true
|
60
|
+
rescue Client::RequestError
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def update!(obj_name, obj_id, params)
|
65
|
+
self.client.request(:put, API_SVC, obj_name_to_path(obj_name) + "/#{obj_id}", params)
|
66
|
+
end
|
67
|
+
|
68
|
+
def obj_name_to_path(obj_name)
|
69
|
+
"/#{obj_name}"
|
70
|
+
end
|
71
|
+
private :obj_name_to_path
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'stackmob'
|
16
|
+
|
17
|
+
module StackMob
|
18
|
+
class Deployer
|
19
|
+
|
20
|
+
attr_accessor :client
|
21
|
+
|
22
|
+
APP_PATH = "heroku/app"
|
23
|
+
|
24
|
+
def initialize(cl)
|
25
|
+
self.client = cl
|
26
|
+
end
|
27
|
+
|
28
|
+
def register(hostname)
|
29
|
+
self.client.request(:post, :api, APP_PATH, :hostname => hostname)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch
|
33
|
+
self.client.request(:get, :api, APP_PATH)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
module Helpers
|
17
|
+
def sm_datastore
|
18
|
+
@sm_datastore ||= begin
|
19
|
+
client = StackMob::Client.new(sm_api_host, sm_app_name, StackMob.env, StackMob.key, StackMob.secret)
|
20
|
+
StackMob::DataStore.new(client)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def sm_push
|
25
|
+
@sm_push ||= begin
|
26
|
+
client = StackMob::Client.new(sm_push_host, sm_app_name, StackMob.env, StackMob.key, StackMob.secret)
|
27
|
+
StackMob::Push.new(client)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sm_api_host
|
32
|
+
sm_normalize_host(sm_hostname_from_header_or_config('HTTP_X_STACKMOB_API'))
|
33
|
+
end
|
34
|
+
private :sm_api_host
|
35
|
+
|
36
|
+
def sm_push_host
|
37
|
+
sm_normalize_host(sm_hostname_from_header_or_config('HTTP_X_STACKMOB_PUSH'))
|
38
|
+
end
|
39
|
+
private :sm_push_host
|
40
|
+
|
41
|
+
def sm_hostname_from_header_or_config(header_str)
|
42
|
+
hostname = request.env[header_str]
|
43
|
+
(hostname.nil? || hostname == "") ? StackMob.dev_url : hostname
|
44
|
+
end
|
45
|
+
private :sm_hostname_from_header_or_config
|
46
|
+
|
47
|
+
def sm_app_name
|
48
|
+
StackMob.app_name
|
49
|
+
end
|
50
|
+
private :sm_app_name
|
51
|
+
|
52
|
+
def sm_app_version
|
53
|
+
(Rails.env.production?) ? StackMob::PRODUCTION : StackMob::SANDBOX
|
54
|
+
end
|
55
|
+
private :sm_app_version
|
56
|
+
|
57
|
+
def sm_normalize_host(host_str)
|
58
|
+
"http://#{StackMob.client_name}.#{host_str}"
|
59
|
+
end
|
60
|
+
private :sm_normalize_host
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
class Push
|
17
|
+
|
18
|
+
attr_accessor :client
|
19
|
+
|
20
|
+
PUSH_SVC = :push
|
21
|
+
DEVICE_TOKEN_PATH = "/device_tokens"
|
22
|
+
BROADCAST_PATH = "/push_broadcast"
|
23
|
+
PUSH_PATH = "/push"
|
24
|
+
|
25
|
+
DEFAULT_BADGE = 0
|
26
|
+
DEFAULT_SOUND = ""
|
27
|
+
|
28
|
+
def initialize(cl)
|
29
|
+
self.client = cl
|
30
|
+
end
|
31
|
+
|
32
|
+
def register(user_id, device_token)
|
33
|
+
self.client.request(:post, PUSH_SVC, DEVICE_TOKEN_PATH, :userId => user_id, :token => device_token) # using non-convential symbols to conform to StackMob Push API
|
34
|
+
end
|
35
|
+
|
36
|
+
def broadcast(opts)
|
37
|
+
aps_data = generate_aps_data(opts)
|
38
|
+
payload = {:recipients => [], :aps => aps_data, :areRecipientsDeviceTokens => true, :exclude_tokens => []}
|
39
|
+
|
40
|
+
self.client.request(:post, PUSH_SVC, BROADCAST_PATH, payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_message(to, opts)
|
44
|
+
aps_data = generate_aps_data(opts)
|
45
|
+
recipients_are_device_tokens = (opts[:recipients_are_users]) ? false : true
|
46
|
+
payload = {:recipients => Array(to), :aps => aps_data, :areRecipientsDeviceTokens => recipients_are_device_tokens, :exclude_tokens => []}
|
47
|
+
|
48
|
+
self.client.request(:post, PUSH_SVC, PUSH_PATH, payload)
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_aps_data(opts)
|
52
|
+
alert = opts[:alert] || (raise ArgumentError.new("Push requires alert message"))
|
53
|
+
badge = opts[:badge] || DEFAULT_BADGE
|
54
|
+
sound = opts[:sound] || DEFAULT_SOUND
|
55
|
+
|
56
|
+
{:badge => badge, :sound => sound, :alert => alert}
|
57
|
+
end
|
58
|
+
private :generate_aps_data
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|