sparkmotion 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ .rake_tasks~
2
+ pkg/*
3
+ build/
4
+ .DS_Store
5
+ .repl_history
6
+ 0
7
+ *.gem
8
+ *.rbc
9
+ .bundle
10
+ .config
11
+ .yardoc
12
+ Gemfile.lock
13
+ InstalledFiles
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sparkmotion.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Marc Ignacio
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Sparkmotion
2
+
3
+ - RubyMotion gem for Spark API
4
+ - currently uses OAuth2 authorization scheme (http://sparkplatform.com/docs/authentication/oauth2_authentication)
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'sparkmotion'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install sparkmotion
19
+
20
+ ## Usage
21
+
22
+ Coming soon
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,280 @@
1
+ module SparkMotion
2
+ class OAuth2Client
3
+ include BW::KVO
4
+
5
+ @@instances = []
6
+ @observed = false
7
+ @@request_retries = 0
8
+
9
+ def self.instances
10
+ @@instances ||= []
11
+ end
12
+
13
+ def self.first
14
+ self.instances.first
15
+ end
16
+
17
+ VALID_OPTION_KEYS = [
18
+ :api_key,
19
+ :api_secret,
20
+ :api_user,
21
+ :endpoint,
22
+ :auth_endpoint,
23
+ :auth_grant_url,
24
+ :callback,
25
+ :user_agent,
26
+ :version,
27
+ :ssl
28
+ ]
29
+
30
+ # keys that are usually updated from Spark in order to access their API
31
+ ACCESS_KEYS = [
32
+ :authorization_code,
33
+ :access_token,
34
+ :refresh_token,
35
+ :expires_in
36
+ ]
37
+
38
+ attr_accessor *VALID_OPTION_KEYS
39
+ attr_accessor :authorized
40
+ attr_accessor *ACCESS_KEYS
41
+
42
+ DEBUGGER = [:d1, :d2]
43
+ attr_accessor *DEBUGGER
44
+
45
+ DEFAULT = {
46
+ api_key: nil,
47
+ api_secret: nil,
48
+ api_user: nil,
49
+ callback: "https://sparkplatform.com/oauth2/callback",
50
+ endpoint: "https://developers.sparkapi.com", # change to https://api.developers.sparkapi.com for production
51
+ auth_endpoint: "https://sparkplatform.com/oauth2", # Ignored for Spark API Auth
52
+ auth_grant_url: "https://api.sparkapi.com/v1/oauth2/grant",
53
+ version: "v1",
54
+ user_agent: "Spark API RubyMotion Gem #{VERSION}",
55
+ ssl: true,
56
+
57
+ authorization_code: nil,
58
+ access_token: nil,
59
+ refresh_token: nil,
60
+ expires_in: 0
61
+ }
62
+
63
+ X_SPARK_API_USER_AGENT = "X-SparkApi-User-Agent"
64
+
65
+ def initialize opts={}
66
+ puts "#{self} initializing..."
67
+ @@instances << self
68
+ (VALID_OPTION_KEYS + ACCESS_KEYS).each do |key|
69
+ send("#{key.to_s}=", DEFAULT[key])
70
+ end
71
+ end
72
+
73
+ # Sample Usage:
74
+ # client = SparkMotion::OAuth2Client.new
75
+ # client.configure do |config|
76
+ # config.api_key = "e8dx727d5padwh6dh1lydapic"
77
+ # config.api_secret = "2d6w2rqwisv0o9dxovhp6g98b"
78
+ # config.callback = "https://sparkplatform.com/oauth2/callback"
79
+ # config.auth_endpoint = "https://sparkplatform.com/oauth2"
80
+ # config.endpoint = 'https://developers.sparkapi.com'
81
+ # end
82
+ def configure
83
+ yield self
84
+ self
85
+ end
86
+
87
+ def get_user_permission &block
88
+ # app opens Mobile Safari and waits for a callback?code=<authorization_code>
89
+ # <authorization_code> is then assigned to client.authorization_code
90
+
91
+ url = "#{self.auth_endpoint}?response_type=code&client_id=#{self.api_key}&redirect_uri=#{self.callback}"
92
+ UIApplication.sharedApplication.openURL NSURL.URLWithString url
93
+
94
+ # AppDelegate#application:handleOpenURL will assign the new authorization code
95
+ unless @observed
96
+ observe(self, "authorization_code") do |old_value, new_value|
97
+ self.authorize &block
98
+ @observed = true
99
+ end
100
+ end
101
+
102
+ return # so that authorization_code will not be printed in output
103
+ end
104
+
105
+ def authorize &block
106
+ callback = auth_response_handler
107
+ options = {payload: setup_payload, headers: setup_headers}
108
+
109
+ block ||= -> { puts "SparkMotion: default callback."}
110
+ BW::HTTP.post(auth_grant_url, options) do |response|
111
+ callback.call response, block
112
+ end
113
+ end
114
+
115
+ alias_method :refresh, :authorize
116
+
117
+ # Usage:
118
+ # client.get(url, options <Hash>)
119
+ # url<String>
120
+ # - url without the Spark API endpoint e.g. '/listings', '/my/listings'
121
+ # - endpoint can be configured in `configure` method
122
+ # options<Hash>
123
+ # - options used for the query
124
+ # :payload<String> - data to pass to a POST, PUT, DELETE query. Additional parameters to
125
+ # :headers<Hash> - headers send with the request
126
+ # - for more info in options, see BW::HTTP.get method in https://github.com/rubymotion/BubbleWrap/blob/master/motion/http.rb
127
+ #
128
+ # Example:
129
+ # if client = SparkMotion::OAuth2Client.new
130
+ #
131
+ # for GET request https://developers.sparkapi.com/v1/listings?_limit=1
132
+ # client.get '/listings', {:payload => {:"_limit" => 1}}
133
+ #
134
+ # for GET request https://developers.sparkapi.com/v1/listings?_limit=1&_filter=PropertyType%20Eq%20'A'
135
+ # client.get '/listings', {:payload => {:_limit => 1, :_filter => "PropertyType Eq 'A'"}}
136
+ def get(spark_url, options={}, &block) # Future TODO: post, put
137
+ # https://<spark_endpoint>/<api version>/<spark resource>
138
+ complete_url = self.endpoint + "/#{version}" + spark_url
139
+
140
+ headers = {
141
+ :"User-Agent" => "MotionSpark RubyMotion Sample App",
142
+ :"X-SparkApi-User-Agent" => "MotionSpark RubyMotion Sample App",
143
+ :"Authorization" => "OAuth #{self.access_token}"
144
+ }
145
+
146
+ opts={}
147
+ opts.merge!(options)
148
+ opts.merge!({:headers => headers})
149
+
150
+ block ||= lambda { |returned|
151
+ puts("SparkMotion: default callback")
152
+ }
153
+
154
+ request = lambda {
155
+ BW::HTTP.get(complete_url, opts) do |response|
156
+ puts "SparkMotion: [status code #{response.status_code}] [#{spark_url}]"
157
+
158
+ response_body = response.body ? response.body.to_str : ""
159
+
160
+ if response.status_code == 200
161
+ puts 'SparkMotion: Successful request.'
162
+
163
+ @@request_retries = 0
164
+ block.call(response_body)
165
+ elsif @@request_retries > 0
166
+ puts "SparkMotion: retried authorization, but failed."
167
+ elsif @@request_retries == 0
168
+ puts("SparkMotion: [status code #{response.status_code}] - Now retrying to establish authorization...")
169
+
170
+ self.authorize do
171
+ puts "SparkMotion: Will now retry the request [#{spark_url}]"
172
+
173
+ @@request_retries += 1
174
+ self.get(spark_url, opts, &block)
175
+ end
176
+ end
177
+ end
178
+ }
179
+
180
+ if authorized?
181
+ puts "SparkMotion: Requesting [#{spark_url}]"
182
+ request.call
183
+ elsif !authorized?
184
+ puts 'SparkMotion: Authorization required. Falling back to authorization before requesting...'
185
+ # TODO: get user permission first before trying #authorize...
186
+ self.get_user_permission(&request)
187
+ end
188
+ end
189
+
190
+ def logout
191
+ self.expires_in = 0
192
+ self.refresh_token = self.access_token = nil
193
+ end
194
+
195
+ def authorized?
196
+ # a string is truthy, but this should not return the refresh token
197
+ self.refresh_token && self.authorized ? true : false
198
+ end
199
+
200
+ private
201
+
202
+ # payload common to `authorize` and `refresh`
203
+ def setup_payload
204
+ payload = {
205
+ client_id: self.api_key,
206
+ client_secret: self.api_secret,
207
+ redirect_uri: self.callback,
208
+ }
209
+
210
+ if authorized?
211
+ puts "SparkMotion: Previously authorized. Refreshing tokens..."
212
+ payload[:refresh_token] = self.refresh_token
213
+ payload[:grant_type] = "refresh_token"
214
+ elsif self.authorization_code
215
+ puts "SparkMotion: Seeking authorization..."
216
+ payload[:code] = self.authorization_code
217
+ payload[:grant_type] = "authorization_code"
218
+ end
219
+
220
+ payload
221
+ end
222
+
223
+ def setup_headers
224
+ # http://sparkplatform.com/docs/api_services/read_first
225
+ # These headers are required when requesting from the API
226
+ # otherwise the request will return an error response.
227
+ headers = {
228
+ :"User-Agent" => "MotionSpark RubyMotion Sample App",
229
+ :"X-SparkApi-User-Agent" => "MotionSpark RubyMotion Sample App"
230
+ }
231
+ end
232
+
233
+ def auth_response_handler
234
+ lambda { |response, block|
235
+ response_json = response.body ? response.body.to_str : ""
236
+ response_body = BW::JSON.parse(response_json)
237
+ if response.status_code == 200 # success
238
+ # usual response:
239
+ # {"expires_in":86400,"refresh_token":"bkaj4hxnyrcp4jizv6epmrmin","access_token":"41924s8kb4ot8cy238doi8mbv"}"
240
+
241
+ self.access_token = response_body["access_token"]
242
+ self.refresh_token = response_body["refresh_token"]
243
+ self.expires_in = response_body["expires_in"]
244
+ puts "SparkMotion: [status code 200] - Client is now authorized to make requests."
245
+
246
+ self.authorized = true
247
+
248
+ block.call if block && block.respond_to?(:call)
249
+ else
250
+ # usual response:
251
+ # {"error_description":"The access grant you supplied is invalid","error":"invalid_grant"}
252
+
253
+ # TODO: handle error in requests better.
254
+ # - there should be a fallback strategy
255
+ # - try to authorize again? (without going through a loop)
256
+ # - SparkMotion::Error module
257
+ puts "SparkMotion: ERROR [status code #{response.status_code}] - Authorization Unsuccessful - response body: #{response_body["error_description"]}"
258
+ self.authorized = false
259
+ end
260
+ }
261
+ end
262
+ end
263
+ end
264
+
265
+ # handle url from safari to get authorization_code during OAuth2Client#get_user_permission
266
+ class AppDelegate
267
+ def application(app, handleOpenURL:url)
268
+ query = url.query_to_hash
269
+ client = SparkMotion::OAuth2Client.first
270
+ client.authorization_code = query["code"]
271
+ return
272
+ end
273
+ end
274
+
275
+ class NSURL
276
+ def query_to_hash
277
+ query_arr = self.query.split(/&|=/)
278
+ query = Hash[*query_arr] # turns [key1,value1,key2,value2] to {key1=>value1, key2=>value2}
279
+ end
280
+ end
@@ -0,0 +1,3 @@
1
+ module SparkMotion
2
+ VERSION = "0.0.1.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ Motion::Project::App.setup do |app|
6
+ Dir.glob(File.join(File.dirname(__FILE__), 'sparkmotion/*.rb')).each do |file|
7
+ app.files.unshift(file)
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sparkmotion/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "sparkmotion"
8
+ gem.version = SparkMotion::VERSION
9
+ gem.authors = ["Marc Ignacio"]
10
+ gem.email = ["marc@aelogica.com", "marcrendlignacio@gmail.com"]
11
+ gem.description = %q{RubyMotion gem for Spark API}
12
+ gem.summary = %q{Currently supports OAuth2 authorization scheme (http://sparkplatform.com/docs/authentication/oauth2_authentication)}
13
+ gem.homepage = "https://github.com/padi/SparkMotion"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ # gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ # gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'bubble-wrap', '~>1.1.4'
21
+ gem.add_development_dependency 'rake'
22
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sparkmotion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Marc Ignacio
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bubble-wrap
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.4
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: RubyMotion gem for Spark API
47
+ email:
48
+ - marc@aelogica.com
49
+ - marcrendlignacio@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/sparkmotion.rb
60
+ - lib/sparkmotion/sparkmotion.rb
61
+ - lib/sparkmotion/version.rb
62
+ - sparkmotion.gemspec
63
+ homepage: https://github.com/padi/SparkMotion
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.24
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Currently supports OAuth2 authorization scheme (http://sparkplatform.com/docs/authentication/oauth2_authentication)
87
+ test_files: []