meteor-motion 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.repl_history +0 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +166 -0
- data/Rakefile +17 -0
- data/app/app_delegate.rb +11 -0
- data/app/controllers/book_controller.rb +92 -0
- data/app/controllers/book_list_controller.rb +105 -0
- data/app/controllers/connection_controller.rb +83 -0
- data/app/controllers/login_controller.rb +35 -0
- data/lib/meteor-motion.rb +12 -0
- data/lib/meteor-motion/version.rb +3 -0
- data/meteor-motion.gemspec +27 -0
- data/motion/adapters/motion_model.rb +61 -0
- data/motion/client.rb +179 -0
- data/motion/collection.rb +50 -0
- data/motion/collections/default.rb +56 -0
- data/motion/collections/motion_model.rb +52 -0
- data/motion/ddp.rb +161 -0
- data/motion/srp/securerandom.rb +248 -0
- data/motion/srp/srp.rb +250 -0
- data/spec/adapters/motion_model_spec.rb +38 -0
- data/spec/client_spec.rb +104 -0
- data/spec/collection_spec.rb +63 -0
- data/spec/collections/default_spec.rb +46 -0
- data/spec/collections/motion_model_spec.rb +69 -0
- data/spec/ddp_spec.rb +123 -0
- data/spec/server/.meteor/.gitignore +1 -0
- data/spec/server/.meteor/packages +9 -0
- data/spec/server/.meteor/release +1 -0
- data/spec/server/collections/books.js +11 -0
- data/spec/server/server/fixtures.js +28 -0
- data/spec/server/server/publications.js +3 -0
- data/spec/server/smart.json +3 -0
- data/vendor/SocketRocket/NSData+SRB64Additions.h +24 -0
- data/vendor/SocketRocket/NSData+SRB64Additions.m +39 -0
- data/vendor/SocketRocket/SRWebSocket.h +114 -0
- data/vendor/SocketRocket/SRWebSocket.m +1757 -0
- data/vendor/SocketRocket/SocketRocket-Prefix.pch +27 -0
- data/vendor/SocketRocket/SocketRocket.bridgesupport +160 -0
- data/vendor/SocketRocket/base64.c +314 -0
- data/vendor/SocketRocket/base64.h +34 -0
- metadata +190 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
class ConnectionController < UIViewController
|
2
|
+
|
3
|
+
def viewDidLoad
|
4
|
+
super
|
5
|
+
|
6
|
+
self.view.backgroundColor = UIColor.whiteColor
|
7
|
+
@label = UILabel.alloc.initWithFrame(CGRectZero)
|
8
|
+
@label.text = "Connecting..."
|
9
|
+
@label.sizeToFit
|
10
|
+
@label.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
|
11
|
+
self.view.addSubview @label
|
12
|
+
|
13
|
+
@meteorClient = MeteorMotion::Client.new
|
14
|
+
@meteorClient.on_error( self.method(:meteor_error) )
|
15
|
+
@meteorClient.connect 'localhost', 3000, self.method(:connected)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def connected result, details
|
20
|
+
if result
|
21
|
+
@meteorClient.add_collection('books')
|
22
|
+
@meteorClient.subscribe('books')
|
23
|
+
|
24
|
+
form = buildLoginForm
|
25
|
+
|
26
|
+
@controller = LoginController.alloc.initWithForm(form)
|
27
|
+
@controller.meteor = @meteorClient
|
28
|
+
|
29
|
+
self.presentViewController( @controller, animated: true, completion: nil )
|
30
|
+
else
|
31
|
+
exit(0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Meteor basic error handler - alert error to screen
|
36
|
+
#
|
37
|
+
def meteor_error code, reason, details
|
38
|
+
if reason != :unknown
|
39
|
+
alert = UIAlertView.new
|
40
|
+
alert.message = reason.to_s
|
41
|
+
alert.addButtonWithTitle "OK"
|
42
|
+
alert.show
|
43
|
+
else
|
44
|
+
puts "Meteor Error: #{reason}. Details: #{details}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def buildLoginForm
|
50
|
+
form = Formotion::Form.new
|
51
|
+
|
52
|
+
form.build_section do |section|
|
53
|
+
section.title = "Credentials"
|
54
|
+
|
55
|
+
section.build_row do |row|
|
56
|
+
row.title = "Username"
|
57
|
+
row.key = :user
|
58
|
+
row.type = :string
|
59
|
+
row.auto_correction = :no
|
60
|
+
row.auto_capitalization = :none
|
61
|
+
row.placeholder = 'Your username'
|
62
|
+
end
|
63
|
+
|
64
|
+
section.build_row do |row|
|
65
|
+
row.title = "Password"
|
66
|
+
row.key = :pass
|
67
|
+
row.type = :string
|
68
|
+
row.secure = true
|
69
|
+
row.auto_correction = :no
|
70
|
+
row.auto_capitalization = :none
|
71
|
+
row.placeholder = 'Your password'
|
72
|
+
end
|
73
|
+
|
74
|
+
section.build_row do |row|
|
75
|
+
row.title = 'Submit'
|
76
|
+
row.type = :submit
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
return form
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class LoginController < Formotion::FormController
|
2
|
+
attr_accessor :meteor
|
3
|
+
|
4
|
+
def viewDidLoad
|
5
|
+
super
|
6
|
+
|
7
|
+
self.view.backgroundColor = UIColor.whiteColor
|
8
|
+
self.title = "Login"
|
9
|
+
|
10
|
+
self.form.on_submit do |form|
|
11
|
+
data = form.render
|
12
|
+
self.view.endEditing(true)
|
13
|
+
|
14
|
+
@meteor.login_with_username( data[:user], data[:pass], self.method(:login_handler) )
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def login_handler action, details
|
20
|
+
if action == :success
|
21
|
+
controller = BookListController.alloc.initWithNibName(nil, bundle: nil)
|
22
|
+
controller.meteor = @meteor
|
23
|
+
navigationController = UINavigationController.alloc.initWithRootViewController(controller)
|
24
|
+
|
25
|
+
self.presentViewController(navigationController , animated: true, completion: nil)
|
26
|
+
|
27
|
+
elsif action == :error
|
28
|
+
alert = UIAlertView.new
|
29
|
+
alert.message = "Error: #{details['reason'].to_s}"
|
30
|
+
alert.addButtonWithTitle "OK"
|
31
|
+
alert.show
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "meteor-motion/version"
|
2
|
+
BW.require 'motion/**/*.rb'
|
3
|
+
|
4
|
+
unless defined?(Motion::Project::Config)
|
5
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
6
|
+
end
|
7
|
+
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
app.vendor_project(File.expand_path(File.join(File.dirname(__FILE__), '../vendor/SocketRocket')), :static, cflags: "-fobjc-arc")
|
10
|
+
app.libs += ['/usr/lib/libicucore.dylib']
|
11
|
+
app.frameworks += ['CFNetwork', 'Security', 'Foundation']
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'meteor-motion/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "meteor-motion"
|
8
|
+
spec.version = MeteorMotion::VERSION
|
9
|
+
spec.authors = ["Miguel Tavares"]
|
10
|
+
spec.email = ["mtavares.azrael@gmail.com"]
|
11
|
+
spec.description = %q{Write a gem description}
|
12
|
+
spec.summary = %q{Write a gem summary}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'bubble-wrap'
|
22
|
+
spec.add_dependency 'rm-digest'
|
23
|
+
spec.add_dependency 'motion_model'
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "motion-redgreen"
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MeteorMotion
|
2
|
+
module Adapters
|
3
|
+
module MotionModel
|
4
|
+
include ::MotionModel::ArrayModelAdapter
|
5
|
+
|
6
|
+
def self.set_client client
|
7
|
+
@@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# Sends the data to the server and supers for the record to be saved. If saving on the server fails
|
13
|
+
# it has to be dealt with by the user.
|
14
|
+
def do_insert options = {}
|
15
|
+
if !options[:local]
|
16
|
+
attr_hash = build_attr_hash
|
17
|
+
id = SecureRandom.hex
|
18
|
+
@@client.call("/#{self.class.to_s.downcase}/insert", self.method(:handle_update), [{_id: id}.merge(attr_hash)])
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_update options = {}
|
25
|
+
if !options[:local]
|
26
|
+
id = self.get_attr(:id)
|
27
|
+
@@client.call("/#{self.class.to_s.downcase}/update", self.method(:handle_update), [{_id: id}, attr_hash])
|
28
|
+
end
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_delete
|
34
|
+
send_remove
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_remove
|
40
|
+
id = self.get_attr(:id)
|
41
|
+
@@client.call("/#{self.class.to_s.downcase}/remove", self.method(:handle_remove), [{_id: id}])
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_attr_hash
|
45
|
+
attributes.reject{|k,v| k == :id}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Stubs to handle inserts, updates and deletes. Can be overrriden by superclass
|
49
|
+
#
|
50
|
+
def handle_insert action, result; end
|
51
|
+
def handle_update action, result; end
|
52
|
+
def handle_remove action, result; end
|
53
|
+
|
54
|
+
# Nasty, nasty hack to work around inheritance issues. TODO: refactor as adapter.
|
55
|
+
def superclass
|
56
|
+
return Object
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/motion/client.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
module MeteorMotion
|
2
|
+
class Client
|
3
|
+
attr_accessor :collections, :subscriptions, :method_callbacks
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@collections = {}
|
7
|
+
@subscriptions = {}
|
8
|
+
@method_callbacks = {}
|
9
|
+
|
10
|
+
@auth_client = nil
|
11
|
+
@auth_key = nil
|
12
|
+
@username = nil
|
13
|
+
@password = nil
|
14
|
+
|
15
|
+
@ddp = MeteorMotion::DDP.new self
|
16
|
+
@error_handler = nil
|
17
|
+
|
18
|
+
MeteorMotion::Adapters::MotionModel.set_client(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def connect hostname='localhost', port=3000, callback=nil
|
23
|
+
@ddp.connect hostname, port
|
24
|
+
if callback
|
25
|
+
@method_callbacks['connect'] = callback
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_connect status
|
30
|
+
if @method_callbacks['connect']
|
31
|
+
obj = { method: @method_callbacks['connect'], action: status == :success, result: nil}
|
32
|
+
self.performSelectorInBackground('background_method_handler:', withObject: obj)
|
33
|
+
@method_callbacks.delete('connect')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_collection klass, name = ""
|
38
|
+
if klass.is_a? String
|
39
|
+
if @collections[klass]
|
40
|
+
raise "A MeteorMotion::Collection named '#{klass}' already exists."
|
41
|
+
end
|
42
|
+
|
43
|
+
@collections[klass] = MeteorMotion::Collections::Default.new klass
|
44
|
+
else
|
45
|
+
coll = MeteorMotion::Collections::MotionModel.new klass, name
|
46
|
+
|
47
|
+
@collections[coll.name] = coll
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def remove_collection name
|
53
|
+
@collections.delete(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def subscribe pub_name, params=[]
|
58
|
+
sub_id = @ddp.sub(pub_name, params)
|
59
|
+
@subscriptions[sub_id] = {name: pub_name, ready: false}
|
60
|
+
|
61
|
+
return sub_id
|
62
|
+
end
|
63
|
+
|
64
|
+
def unsubscribe sub_id
|
65
|
+
@ddp.unsub(sub_id)
|
66
|
+
@subscriptions.delete(sub_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def call method_name, callback, params=[]
|
71
|
+
method_id = @ddp.call(method_name, params)
|
72
|
+
|
73
|
+
@method_callbacks[method_id] = { method: callback, result: false, updated: false}
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Methods for SRP authentication
|
78
|
+
#
|
79
|
+
def login_with_username username, password, callback
|
80
|
+
@method_callbacks['login'] = callback
|
81
|
+
@auth_client = SRP::Client.new
|
82
|
+
@username = username
|
83
|
+
@password = password
|
84
|
+
|
85
|
+
|
86
|
+
a = @auth_client.start_authentication()
|
87
|
+
id = @ddp.call( 'beginPasswordExchange', { 'A' => a, user: { username: username } } )
|
88
|
+
|
89
|
+
@method_callbacks[id] = { method: self.method(:handle_login_challenge), result: false, updated: false }
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle_login_challenge action, result
|
93
|
+
if action == :updated
|
94
|
+
return
|
95
|
+
elsif action == :error
|
96
|
+
obj = { method: @method_callbacks['login'], action: action, result: result }
|
97
|
+
self.performSelectorInBackground('background_method_handler:', withObject: obj)
|
98
|
+
|
99
|
+
@method_callbacks.delete('login')
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
m = @auth_client.process_challenge(result['identity'], @password, result['salt'], result['B'])
|
104
|
+
id = @ddp.call( 'login', {srp: {'M' => m} } )
|
105
|
+
|
106
|
+
@method_callbacks[id] = { method: self.method(:verify_login), result: false, updated: false }
|
107
|
+
end
|
108
|
+
|
109
|
+
def verify_login action, result
|
110
|
+
if action == :updated
|
111
|
+
return
|
112
|
+
elsif action == :error
|
113
|
+
obj = { method: @method_callbacks['login'], action: action, result: result }
|
114
|
+
self.performSelectorInBackground('background_method_handler:', withObject: obj)
|
115
|
+
|
116
|
+
@method_callbacks.delete('login')
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
if @auth_client.verify result['HAMK']
|
121
|
+
obj = { method: @method_callbacks['login'], action: :success, result: nil }
|
122
|
+
else
|
123
|
+
obj = { method: @method_callbacks['login'], action: :error, result: {reason: 'Failed HAMK validation.'} }
|
124
|
+
end
|
125
|
+
|
126
|
+
self.performSelectorInBackground('background_method_handler:', withObject: obj)
|
127
|
+
@method_callbacks.delete('login')
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def on_error method
|
132
|
+
@error_handler = method
|
133
|
+
end
|
134
|
+
|
135
|
+
# Methods required for MeteorMotion::DDP delegation
|
136
|
+
#
|
137
|
+
def handle_method id, action, result
|
138
|
+
callback = @method_callbacks[id]
|
139
|
+
|
140
|
+
if !callback
|
141
|
+
return
|
142
|
+
end
|
143
|
+
|
144
|
+
obj = { method: callback[:method], action: action, result: result}
|
145
|
+
|
146
|
+
self.performSelectorInBackground('background_method_handler:', withObject: obj)
|
147
|
+
|
148
|
+
if callback[action] == :error
|
149
|
+
callback[action] = :result
|
150
|
+
end
|
151
|
+
|
152
|
+
callback[action] = true
|
153
|
+
if callback[:result] && callback[:updated]
|
154
|
+
@method_callbacks.delete(id)
|
155
|
+
else
|
156
|
+
@method_callbacks[id] = callback
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def error code, reason, details
|
161
|
+
if @error_handler
|
162
|
+
obj = { method: @error_handler, code: code, reason: reason, details: details}
|
163
|
+
self.performSelectorInBackground('background_error_handler:', withObject: obj)
|
164
|
+
else
|
165
|
+
#TODO: silent error handling
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
def background_method_handler obj
|
171
|
+
obj[:method].call( obj[:action], obj[:result])
|
172
|
+
end
|
173
|
+
|
174
|
+
def background_error_handler obj
|
175
|
+
obj[:method].call( obj[:code], obj[:reason], obj[:details])
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module MeteorMotion
|
2
|
+
class Collection
|
3
|
+
attr_accessor :name, :observers
|
4
|
+
|
5
|
+
def initialize name
|
6
|
+
@name = name
|
7
|
+
@observers = []
|
8
|
+
end
|
9
|
+
|
10
|
+
# Method hooks - be sure to call super after reimplementing these
|
11
|
+
# Right now these are here more for testing purposes, should be reimplemented as a hook with some testing compromise
|
12
|
+
#
|
13
|
+
def add id, fields
|
14
|
+
notify_observers :added, id
|
15
|
+
end
|
16
|
+
|
17
|
+
def update id, fields, cleared
|
18
|
+
notify_observers :changed, id
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove id
|
22
|
+
notify_observers :removed, id
|
23
|
+
end
|
24
|
+
|
25
|
+
# Observer methods
|
26
|
+
#
|
27
|
+
def add_observer method
|
28
|
+
@observers << method
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove_observer method
|
32
|
+
@observers.delete( method )
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def notify_observers action, id
|
37
|
+
obj = {action: action, id: id}
|
38
|
+
self.performSelectorInBackground('background_notify:', withObject: obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
# These callbacks should be done as background tasks to prevent the connection thread from holding in intensive tasks
|
42
|
+
#
|
43
|
+
def background_notify object
|
44
|
+
observers.each do |method_name|
|
45
|
+
#puts "Calling method with action #{object[:action]}"
|
46
|
+
method_name.call( object[:action], object[:id] )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|