meteor-motion 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.repl_history +0 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +166 -0
  7. data/Rakefile +17 -0
  8. data/app/app_delegate.rb +11 -0
  9. data/app/controllers/book_controller.rb +92 -0
  10. data/app/controllers/book_list_controller.rb +105 -0
  11. data/app/controllers/connection_controller.rb +83 -0
  12. data/app/controllers/login_controller.rb +35 -0
  13. data/lib/meteor-motion.rb +12 -0
  14. data/lib/meteor-motion/version.rb +3 -0
  15. data/meteor-motion.gemspec +27 -0
  16. data/motion/adapters/motion_model.rb +61 -0
  17. data/motion/client.rb +179 -0
  18. data/motion/collection.rb +50 -0
  19. data/motion/collections/default.rb +56 -0
  20. data/motion/collections/motion_model.rb +52 -0
  21. data/motion/ddp.rb +161 -0
  22. data/motion/srp/securerandom.rb +248 -0
  23. data/motion/srp/srp.rb +250 -0
  24. data/spec/adapters/motion_model_spec.rb +38 -0
  25. data/spec/client_spec.rb +104 -0
  26. data/spec/collection_spec.rb +63 -0
  27. data/spec/collections/default_spec.rb +46 -0
  28. data/spec/collections/motion_model_spec.rb +69 -0
  29. data/spec/ddp_spec.rb +123 -0
  30. data/spec/server/.meteor/.gitignore +1 -0
  31. data/spec/server/.meteor/packages +9 -0
  32. data/spec/server/.meteor/release +1 -0
  33. data/spec/server/collections/books.js +11 -0
  34. data/spec/server/server/fixtures.js +28 -0
  35. data/spec/server/server/publications.js +3 -0
  36. data/spec/server/smart.json +3 -0
  37. data/vendor/SocketRocket/NSData+SRB64Additions.h +24 -0
  38. data/vendor/SocketRocket/NSData+SRB64Additions.m +39 -0
  39. data/vendor/SocketRocket/SRWebSocket.h +114 -0
  40. data/vendor/SocketRocket/SRWebSocket.m +1757 -0
  41. data/vendor/SocketRocket/SocketRocket-Prefix.pch +27 -0
  42. data/vendor/SocketRocket/SocketRocket.bridgesupport +160 -0
  43. data/vendor/SocketRocket/base64.c +314 -0
  44. data/vendor/SocketRocket/base64.h +34 -0
  45. metadata +190 -0
@@ -0,0 +1,56 @@
1
+ module MeteorMotion
2
+ module Collections
3
+ class Default < MeteorMotion::Collection
4
+
5
+ def initialize name
6
+ super
7
+ @objects = {}
8
+ end
9
+
10
+ def add id, fields
11
+ @objects[id] = fields
12
+ super
13
+ end
14
+
15
+
16
+ def update id, fields, cleared
17
+ obj = @objects[id].mutableCopy
18
+
19
+ if fields
20
+ fields.each do |k, v|
21
+ obj[k] = v
22
+ end
23
+ end
24
+
25
+ if cleared
26
+ cleared.each do |key|
27
+ obj.delete(key)
28
+ end
29
+ end
30
+
31
+ @objects[id] = obj
32
+
33
+ super
34
+ end
35
+
36
+
37
+ def remove id
38
+ @objects.delete(id)
39
+ super
40
+ end
41
+
42
+ def all
43
+ @objects.map { |k,v| v.merge({:_id => k }) }
44
+ end
45
+
46
+ def find id
47
+ return @objects[id]
48
+ end
49
+
50
+ def size
51
+ return @objects.size
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ module MeteorMotion
2
+ module Collections
3
+ class MotionModel < MeteorMotion::Collection
4
+
5
+ def initialize klass, aka=""
6
+ # if !(klass < ::MeteorMotion::Adapters::MotionModel)
7
+ # raise 'Adapter not compatible with collection'
8
+ # end
9
+
10
+ @klass = klass
11
+ name = aka == "" ? klass.to_s.downcase : aka
12
+
13
+ super name
14
+ end
15
+
16
+ def add id, fields
17
+ obj = find(id)
18
+ puts "ADDING: #{obj}"
19
+ if obj
20
+ obj.save({local: true})
21
+ else
22
+ obj = @klass.new({id: id}.merge(fields))
23
+ obj.save({local: true})
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ def update id, fields, cleared
30
+ obj = find(id)
31
+ obj.attributes = fields
32
+ obj.save({local: true})
33
+
34
+ super
35
+ end
36
+
37
+ def remove id
38
+ obj = find(id)
39
+ if obj
40
+ obj.destroy({local: true})
41
+ end
42
+
43
+ super
44
+ end
45
+
46
+ def find id
47
+ obj = @klass.where(:id).eq(id).first
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,161 @@
1
+ module MeteorMotion
2
+ class DDP
3
+ attr_accessor :session, :error_handler
4
+
5
+ def initialize delegate
6
+ @ws = nil
7
+ @status = :closed
8
+ @session = nil
9
+ @call_id = 0
10
+
11
+ @delegate = delegate
12
+ end
13
+
14
+
15
+ # Open up the websocket to the provided hostname and start listening for messages
16
+ #
17
+ def connect hostname="localhost", port=3000
18
+ @ws = SRWebSocket.new
19
+ @ws.initWithURL( NSURL.alloc.initWithString "http://#{hostname}:#{port}/websocket" )
20
+ @ws.delegate = self
21
+ @ws.open
22
+ end
23
+
24
+
25
+ # Subscribe to the publication specified by pub_name
26
+ #
27
+ def sub pub_name, params = []
28
+ id = SecureRandom.hex
29
+ msg = {msg: 'sub', id: id, name: pub_name, params: build_params(params) }
30
+ send_message msg
31
+
32
+ return id
33
+ end
34
+
35
+
36
+ # Unsubscribe to the publication with the specified id
37
+ #
38
+ def unsub id
39
+ msg = {msg: 'unsub', id: id}
40
+ send_message msg
41
+ end
42
+
43
+
44
+ # Call the specified method_name on the server, with optional params
45
+ #
46
+ def call method_name, params=[]
47
+ id = @call_id.to_s
48
+ @call_id = @call_id.next
49
+
50
+ msg = {msg: 'method', method: method_name, params: build_params(params), id: id}
51
+ send_message msg
52
+
53
+ return id
54
+ end
55
+
56
+
57
+ def websocket_ready?
58
+ @status == :socket_ready || @status == :connected
59
+ end
60
+
61
+ # Implementation of SRWebSocketDelegate to handle SRWebSocket callbacks
62
+ #
63
+ def webSocket(webSocket, didReceiveMessage: message)
64
+ handle_message message
65
+ end
66
+
67
+ def webSocketDidOpen ws
68
+ @status = :socket_ready
69
+ msg = {msg: 'connect',version: 'pre1', support: ['pre1']}
70
+ send_message msg
71
+ end
72
+
73
+ def didFailWithError error
74
+ puts "WebSocket send failed. Error code: #{error.code}"
75
+ end
76
+
77
+ def didCloseWithCode code, reason, was_clean
78
+ @status = :closed
79
+ puts "WebSocket closed"
80
+ end
81
+ #
82
+ # End of SRWebsocketDelegate implementation
83
+
84
+
85
+
86
+ private
87
+ def send_message msg
88
+ puts "==> " + BW::JSON.generate(msg).to_s
89
+ @ws.send BW::JSON.generate(msg)
90
+ end
91
+
92
+ def handle_message msg
93
+ puts "<== " + msg.to_s
94
+ json_string = msg.dataUsingEncoding(NSUTF8StringEncoding)
95
+ e = Pointer.new(:object)
96
+ data = NSJSONSerialization.JSONObjectWithData(json_string, options:0, error: e)
97
+
98
+ case data[:msg]
99
+ when 'connected'
100
+ @status = :connected
101
+ @session = data[:session]
102
+ @delegate.handle_connect :success
103
+ when 'failed'
104
+ #TODO: Handle failed connections better
105
+ @delegate.handle_connect :failed
106
+ when 'nosub'
107
+ if data[:error]
108
+ @delegate.error( data[:error][:error], data[:error][:reason], data[:error][:details] )
109
+ end
110
+ when 'added', 'changed', 'removed'
111
+ collection = @delegate.collections[data[:collection]]
112
+
113
+ if collection
114
+ if data[:msg] == 'added'
115
+ collection.add( data[:id], data[:fields])
116
+ elsif data[:msg] == 'changed'
117
+ collection.update( data[:id], data[:fields], data[:cleared] )
118
+ elsif data[:msg] == 'removed'
119
+ collection.remove( data[:id] )
120
+ end
121
+ else
122
+ #TODO: Handle data that does not have a collection to reside in
123
+ end
124
+ when 'ready'
125
+ data[:subs].each do |sub_id|
126
+ @delegate.subscriptions[sub_id][:ready] = true
127
+ end
128
+ when 'result'
129
+ if data[:error]
130
+ @delegate.handle_method( data[:id], :error, data[:error] )
131
+ else
132
+ @delegate.handle_method( data[:id], :result, data[:result] )
133
+ end
134
+ when 'updated'
135
+ data[:methods].each do |method_id|
136
+ @delegate.handle_method( method_id, :updated, nil )
137
+ end
138
+ when 'error'
139
+ @delegate.error( nil, data[:reason], data[:offendingMessage] )
140
+
141
+ else
142
+ @delegate.error( nil, :unknown, msg)
143
+ end
144
+
145
+ end
146
+
147
+ def build_params params
148
+ if params == [] || !params
149
+ return []
150
+ end
151
+
152
+ #return params.each.map {|k,v| {k => v} }
153
+ if params.kind_of?(Array)
154
+ return params
155
+ else
156
+ return [params]
157
+ end
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,248 @@
1
+ # = Secure random number generator interface.
2
+ #
3
+ # This library is an interface for secure random number generator which is
4
+ # suitable for generating session key in HTTP cookies, etc.
5
+ #
6
+ # It supports following secure random number generators.
7
+ #
8
+ # * openssl
9
+ # * /dev/urandom
10
+ # * Win32
11
+ #
12
+ # == Example
13
+ #
14
+ # # random hexadecimal string.
15
+ # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
16
+ # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
17
+ # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
18
+ # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
19
+ # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
20
+ # ...
21
+ #
22
+ # # random base64 string.
23
+ # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
24
+ # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
25
+ # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
26
+ # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
27
+ # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
28
+ # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
29
+ # ...
30
+ #
31
+ # # random binary string.
32
+ # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
33
+ # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
34
+ # ...
35
+
36
+ module SecureRandom
37
+ # SecureRandom.random_bytes generates a random binary string.
38
+ #
39
+ # The argument n specifies the length of the result string.
40
+ #
41
+ # If n is not specified, 16 is assumed.
42
+ # It may be larger in future.
43
+ #
44
+ # The result may contain any byte: "\x00" - "\xff".
45
+ #
46
+ # p SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
47
+ # p SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
48
+ #
49
+ # If secure random number generator is not available,
50
+ # NotImplementedError is raised.
51
+ def self.random_bytes(n=nil)
52
+ n ||= 16
53
+
54
+ if !defined?(@has_urandom) || @has_urandom
55
+ flags = File::RDONLY
56
+ flags |= File::NONBLOCK if defined? File::NONBLOCK
57
+ flags |= File::NOCTTY if defined? File::NOCTTY
58
+ begin
59
+ File.open("/dev/urandom", flags) {|f|
60
+ unless f.stat.chardev?
61
+ raise Errno::ENOENT
62
+ end
63
+ @has_urandom = true
64
+ ret = f.readpartial(n)
65
+ if ret.length != n
66
+ raise NotImplementedError, "Unexpected partial read from random device"
67
+ end
68
+ return ret
69
+ }
70
+ rescue Errno::ENOENT
71
+ @has_urandom = false
72
+ end
73
+ end
74
+
75
+ if !defined?(@has_win32)
76
+ begin
77
+ require 'Win32API'
78
+
79
+ crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
80
+ @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
81
+
82
+ hProvStr = " " * 4
83
+ prov_rsa_full = 1
84
+ crypt_verifycontext = 0xF0000000
85
+
86
+ if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
87
+ raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
88
+ end
89
+ @hProv, = hProvStr.unpack('L')
90
+
91
+ @has_win32 = true
92
+ rescue LoadError
93
+ @has_win32 = false
94
+ end
95
+ end
96
+ if @has_win32
97
+ bytes = " ".force_encoding("ASCII-8BIT") * n
98
+ if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
99
+ raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
100
+ end
101
+ return bytes
102
+ end
103
+
104
+ raise NotImplementedError, "No random device"
105
+ end
106
+
107
+ # SecureRandom.hex generates a random hex string.
108
+ #
109
+ # The argument n specifies the length of the random length.
110
+ # The length of the result string is twice of n.
111
+ #
112
+ # If n is not specified, 16 is assumed.
113
+ # It may be larger in future.
114
+ #
115
+ # The result may contain 0-9 and a-f.
116
+ #
117
+ # p SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
118
+ # p SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61"
119
+ #
120
+ # If secure random number generator is not available,
121
+ # NotImplementedError is raised.
122
+ def self.hex(n=nil)
123
+ random_bytes(n).unpack("H*")[0]
124
+ end
125
+
126
+ # SecureRandom.base64 generates a random base64 string.
127
+ #
128
+ # The argument n specifies the length of the random length.
129
+ # The length of the result string is about 4/3 of n.
130
+ #
131
+ # If n is not specified, 16 is assumed.
132
+ # It may be larger in future.
133
+ #
134
+ # The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
135
+ #
136
+ # p SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
137
+ # p SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
138
+ #
139
+ # If secure random number generator is not available,
140
+ # NotImplementedError is raised.
141
+ #
142
+ # See RFC 3548 for base64.
143
+ def self.base64(n=nil)
144
+ [random_bytes(n)].pack("m*").delete("\n")
145
+ end
146
+
147
+ # SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
148
+ #
149
+ # The argument _n_ specifies the length of the random length.
150
+ # The length of the result string is about 4/3 of _n_.
151
+ #
152
+ # If _n_ is not specified, 16 is assumed.
153
+ # It may be larger in future.
154
+ #
155
+ # The boolean argument _padding_ specifies the padding.
156
+ # If it is false or nil, padding is not generated.
157
+ # Otherwise padding is generated.
158
+ # By default, padding is not generated because "=" may be used as a URL delimiter.
159
+ #
160
+ # The result may contain A-Z, a-z, 0-9, "-" and "_".
161
+ # "=" is also used if _padding_ is true.
162
+ #
163
+ # p SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
164
+ # p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
165
+ #
166
+ # p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
167
+ # p SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
168
+ #
169
+ # If secure random number generator is not available,
170
+ # NotImplementedError is raised.
171
+ #
172
+ # See RFC 3548 for URL-safe base64.
173
+ def self.urlsafe_base64(n=nil, padding=false)
174
+ s = [random_bytes(n)].pack("m*")
175
+ s.delete!("\n")
176
+ s.tr!("+/", "-_")
177
+ s.delete!("=") if !padding
178
+ s
179
+ end
180
+
181
+ # SecureRandom.random_number generates a random number.
182
+ #
183
+ # If an positive integer is given as n,
184
+ # SecureRandom.random_number returns an integer:
185
+ # 0 <= SecureRandom.random_number(n) < n.
186
+ #
187
+ # p SecureRandom.random_number(100) #=> 15
188
+ # p SecureRandom.random_number(100) #=> 88
189
+ #
190
+ # If 0 is given or an argument is not given,
191
+ # SecureRandom.random_number returns an float:
192
+ # 0.0 <= SecureRandom.random_number() < 1.0.
193
+ #
194
+ # p SecureRandom.random_number #=> 0.596506046187744
195
+ # p SecureRandom.random_number #=> 0.350621695741409
196
+ #
197
+ def self.random_number(n=0)
198
+ if 0 < n
199
+ hex = n.to_s(16)
200
+ hex = '0' + hex if (hex.length & 1) == 1
201
+ bin = [hex].pack("H*")
202
+ mask = bin[0].ord
203
+ mask |= mask >> 1
204
+ mask |= mask >> 2
205
+ mask |= mask >> 4
206
+ begin
207
+ rnd = SecureRandom.random_bytes(bin.length)
208
+ rnd[0] = (rnd[0].ord & mask).chr
209
+ end until rnd < bin
210
+ rnd.unpack("H*")[0].hex
211
+ else
212
+ # assumption: Float::MANT_DIG <= 64
213
+ i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
214
+ Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
215
+ end
216
+ end
217
+
218
+ # SecureRandom.uuid generates a v4 random UUID (Universally Unique IDentifier).
219
+ #
220
+ # p SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
221
+ # p SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
222
+ # p SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
223
+ #
224
+ # The version 4 UUID is purely random (except the version).
225
+ # It doesn't contain meaningful information such as MAC address, time, etc.
226
+ #
227
+ # See RFC 4122 for details of UUID.
228
+ #
229
+ def self.uuid
230
+ ary = self.random_bytes(16).unpack("NnnnnN")
231
+ ary[2] = (ary[2] & 0x0fff) | 0x4000
232
+ ary[3] = (ary[3] & 0x3fff) | 0x8000
233
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
234
+ end
235
+
236
+ # Following code is based on David Garamond's GUID library for Ruby.
237
+ def self.lastWin32ErrorMessage # :nodoc:
238
+ get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
239
+ format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
240
+ format_message_ignore_inserts = 0x00000200
241
+ format_message_from_system = 0x00001000
242
+
243
+ code = get_last_error.call
244
+ msg = "\0" * 1024
245
+ len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
246
+ msg[0, len].tr("\r", '').chomp
247
+ end
248
+ end