sparkmotion 0.0.1.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.
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: []