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.
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