seapig-router 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3768c130c423cc69ca19d096eeda6c82b8b5358f
4
+ data.tar.gz: 77e72a3c2eea960238e86e1e213e96975796a2c8
5
+ SHA512:
6
+ metadata.gz: c931ea140f573f728241091a7cac85e619a0472629a4d982697bb423d128c2df507287fa79b7e5e095024d79d059e432e185e61fd1e4df905b9b2fa6abdd4ebf
7
+ data.tar.gz: 742b59786fa95ca5a675883d97f9fa76857858553221e908a21e521a38b6d446358fe4d9c267745c83a2e4e5e4d397508aedd7be94c5bd75fc32808f6f11b95e
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015-2017 yunta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'jasmine'
2
+ load 'jasmine/tasks/jasmine.rake'
@@ -0,0 +1,334 @@
1
+ class @SeapigRouter
2
+
3
+
4
+ constructor: (seapig_client, options={})->
5
+ @seapig_client = seapig_client
6
+ @session_id = undefined
7
+ @mountpoint = (options.mountpoint or "/")
8
+ @default_state = (options.default or {})
9
+ @debug = options.debug
10
+ @expose = (options.expose or [])
11
+ @cast = (options.cast or (state)-> state)
12
+ @onsessionopen = options.onsessionopen
13
+
14
+ @token = (String.fromCharCode(48 + code + (if code > 9 then 7 else 0) + (if code > 35 then 6 else 0)) for code in (Math.floor(Math.random()*62) for i in [0..11])).join("")
15
+ console.log('ROUTER: Generated token: ', @token) if @debug
16
+
17
+ @state = undefined
18
+ @state_id = 1
19
+ @state_raw = { session_id: undefined, state_id: 0, state_parent: undefined, state_committed: true }
20
+ @state_valid = false
21
+
22
+ commit_scheduled_at = null
23
+ commit_timer = null
24
+ remote_state = null
25
+
26
+ priv = {}
27
+ @private = priv if options.expose_privates
28
+
29
+ session_data = @seapig_client.master('SeapigRouter::Session::'+@token+'::Data', object: { token: @token, session: @session_id, states: {}})
30
+ session_data.bump()
31
+
32
+ session_data_saved = @seapig_client.slave('SeapigRouter::Session::'+@token+'::Saved')
33
+ session_data_saved.onchange =>
34
+ return if not session_data_saved.valid
35
+ for state_id in _.keys(session_data.object.states)
36
+ delete session_data.object.states[state_id] if parseInt(state_id) < session_data_saved.object.max_state_id
37
+ if not @session_id?
38
+ @session_id = session_data_saved.object.session_id
39
+ @state.session_id = @state_raw.session_id = @session_id if @state? and not @state_raw.session_id?
40
+ console.log('ROUTER: Session opened', @session_id) if @debug
41
+ @onsessionopen(@session_id) if @onsessionopen?
42
+ console.log('ROUTER: Session saved up till:', session_data_saved.object.max_state_id) if @debug
43
+ location_update(false) if @state_valid
44
+
45
+
46
+ document.onclick = (event) =>
47
+ href = (event.target.getAttribute("href") or "")
48
+ console.log('ROUTER: A-element clicked, changing location to:', href) if @debug
49
+ return true if not (href[0] == '?')
50
+ @navigate(href)
51
+ false
52
+
53
+
54
+ window.onpopstate = (event) =>
55
+ @state_raw = JSON.parse(JSON.stringify(event.state))
56
+ @state_raw.session_id = @session_id if not @state_raw.session_id?
57
+ @state = @cast(JSON.parse(JSON.stringify(@state_raw)))
58
+ console.log('ROUTER: History navigation triggered. Going to:', event.state) if @debug
59
+ location_update(false)
60
+ @onchange(@state_raw, previous_state) if @onchange?
61
+
62
+
63
+ state_permanent = (state)=>
64
+ _.omit(state, (value, key)-> key.indexOf("_") == 0 or key == "session_id" or key == "state_id" or key == "state_parent" or key == "state_committed")
65
+
66
+
67
+ state_diff_generate = priv.state_diff_generate = (state1, state2)=>
68
+
69
+ element_diff = (diff, address, object1, object2)->
70
+ same_type = ((typeof object1 == typeof object2) and (Array.isArray(object1) == Array.isArray(object2)))
71
+ if (not object2?) or (object1? and not same_type)
72
+ diff.push ['-'+address, '-']
73
+ object1 = undefined
74
+ if Array.isArray(object2)
75
+ array_diff(diff, address+"~", object1 or [], object2)
76
+ else if typeof object2 == 'object'
77
+ object_diff(diff, address+".", object1 or {}, object2)
78
+ else if object2?
79
+ diff.push [address, object2] if object1 != object2
80
+
81
+ object_diff = (diff, address, object1, object2)->
82
+ for key in _.uniq(_.union(_.keys(object1), _.keys(object2)))
83
+ element_diff(diff, address+key, object1[key], object2[key])
84
+ diff
85
+
86
+ array_diff = (diff, address, array1, array2)->
87
+ j = 0
88
+ for element1, i in array1
89
+ if _.isEqual(element1, array2[j])
90
+ j++
91
+ else
92
+ k = j
93
+ k++ while (not _.isEqual(element1,array2[k])) and (k < array2.length)
94
+ if k == array2.length
95
+ if typeof element1 == 'object'
96
+ diff.push ["-"+address+j+"~", "-"]
97
+ else
98
+ diff.push ["-"+address+"~", element1]
99
+ else
100
+ while j < k
101
+ element_diff(diff, address+j+"~", undefined, array2[j++])
102
+ j++
103
+
104
+ while j < array2.length
105
+ element_diff(diff, address+"~", undefined, array2[j++])
106
+
107
+ object_diff([], "", state1, state2)
108
+
109
+
110
+ state_diff_apply = priv.state_diff_apply = (state, diff)=>
111
+ for entry in diff
112
+ address = entry[0]
113
+ value = entry[1]
114
+ add = (address[0] != '-')
115
+ address = address[1..-1] if address[0] == '-'
116
+ obj = state
117
+ spl = address.split('.')
118
+ for subobj,i in spl
119
+ if i < (spl.length-1)
120
+ if subobj[subobj.length-1] == '~'
121
+ if subobj.split("~")[1].length > 0
122
+ obj[parseInt(subobj.split("~")[1])] = {} if not obj[parseInt(subobj.split("~")[1])]
123
+ obj = obj[parseInt(subobj.split("~")[1])]
124
+ else
125
+ obj[subobj.split("~")[0]] = [new_obj = {}]
126
+ obj = new_obj
127
+ else
128
+ obj[subobj] = {} if not obj[subobj]?
129
+ obj = obj[subobj]
130
+ address = spl[spl.length-1]
131
+ hash = (address[address.length-1] != '~')
132
+ index = undefined
133
+ index = parseInt(address.split('~')[1]) if (not hash) and address.split("~")[1].length > 0
134
+ address = address.split("~")[0]
135
+ if add
136
+ if hash
137
+ obj[address] = value
138
+ else
139
+ if index?
140
+ (obj[address] ||= []).splice(index,0,value)
141
+ else
142
+ (obj[address] ||= []).push(value)
143
+ else
144
+ if hash
145
+ delete obj[address]
146
+ else
147
+ if index?
148
+ obj[address].splice(index,1)
149
+ else
150
+ obj[address].splice(_.indexOf(obj[address], value),1)
151
+ state
152
+
153
+
154
+ url_to_state_description = (pathname, search)=>
155
+
156
+ # URL FORMAT:
157
+ # /VERSION/SESSION_ID/STATE_ID/[EXPOSED_DIFF/][-/BUFFER_DIFF][?CHANGE_DIFF]
158
+ # VERSION - format code
159
+ # SESSION_ID
160
+ # STATE_ID - id of latest state saved on server
161
+ # EXPOSED DIFF - "pretty" part of the url, exposing selected state components for end user manipulation.
162
+ # BUFFER_DIFF - temporary section, holding the difference between STATE_ID state and current state. vanishes after current state gets saved on server.
163
+ # CHANGE_DIFF - temporary section, holding state change intended by <A> link (e.g. href="?view=users&user=10"). vanishes immediately and gets transeferred to BUFFER_DIFF.
164
+
165
+ state_description = { session_id: null, state_id: null, buffer: [], exposed: [], change: [] }
166
+
167
+ spl = pathname.split(@mountpoint)
168
+ spl.shift()
169
+ spl = (decodeURIComponent(part) for part in spl.join(@mountpoint).split('/'))
170
+
171
+ version = spl.shift()
172
+ if version == 'a'
173
+ state_description.session_id = spl.shift()
174
+ state_description.session_id = undefined if state_description.session_id == '_'
175
+ state_description.state_id = spl.shift()
176
+
177
+ if state_description.state_id?
178
+ while spl.length > 0
179
+ key = spl.shift()
180
+ break if key == '-'
181
+ component = _.find @expose, (component)-> component[1]
182
+ next if not component
183
+ state_description.exposed.push([component[0],spl.shift()])
184
+
185
+ while spl.length > 0
186
+ state_description.buffer.push([spl.shift(),spl.shift()])
187
+ else
188
+ state_description.session_id = @session_id
189
+ state_description.state_id = 0
190
+ state_description.buffer = state_diff_generate(state_permanent(@state_raw), state_permanent(@default_state))
191
+
192
+ if search.length > 1
193
+ for pair in search.split('?')[1].split('&')
194
+ decoded_pair = (decodeURIComponent(part) for part in pair.split('=',2))
195
+ state_description.change.push(decoded_pair)
196
+
197
+ console.log('ROUTER: Parsed location', state_description) if @debug
198
+ state_description
199
+
200
+
201
+ state_description_to_url = (state_description)=>
202
+ console.log('ROUTER: Calculating url for state description:', state_description) if @debug
203
+ url = @mountpoint+'a/'+(state_description.session_id or '_')+'/'+state_description.state_id
204
+ url += "/"+(encodeURIComponent(component) for component in _.flatten(state_description.exposed)).join("/") if state_description.exposed.length > 0
205
+ url += "/-/"+(encodeURIComponent(component) for component in _.flatten(state_description.buffer)).join("/") if state_description.buffer.length > 0
206
+ console.log('ROUTER: Calculated url:', url) if @debug
207
+ url
208
+
209
+
210
+ state_set_from_state_description = (state_description, defer, replace)=>
211
+
212
+ state_commit = (replace) =>
213
+ console.log("ROUTER: Committing state:",@state_raw) if @debug
214
+ @state_raw.state_committed = true
215
+ session_data.object.states[@state_raw.state_id] = state_permanent(@state_raw)
216
+ session_data.bump()
217
+ clearTimeout(commit_timer) if commit_timer
218
+ commit_scheduled_at = null
219
+ commit_timer = null
220
+ location_update(replace)
221
+
222
+ commit_needed_at = Date.now() + defer
223
+
224
+ if not @state_raw.state_committed
225
+ last_committed_state = @state_raw.state_parent
226
+ else
227
+ last_committed_state = @state_raw
228
+
229
+ console.log("ROUTER: Changing state. Commit deferred by", defer, "to be done at", commit_needed_at, " State before mutation:",last_committed_state) if @debug
230
+
231
+ previous_state = @state_raw
232
+ new_state = JSON.parse(JSON.stringify(state_permanent(@state_raw)))
233
+ new_state = state_diff_apply(new_state, state_description.buffer)
234
+ new_state = state_diff_apply(new_state, state_description.exposed)
235
+ new_state = state_diff_apply(new_state, state_description.change)
236
+ _.extend(new_state, _.pick(previous_state, (value,key)-> key.indexOf("_") == 0))
237
+
238
+ if state_diff_generate(state_permanent(last_committed_state), state_permanent(new_state)).length > 0
239
+ new_state.state_committed = false
240
+ if previous_state.state_committed
241
+ new_state.session_id = @session_id
242
+ new_state.state_id = @state_id++
243
+ new_state.state_parent = previous_state
244
+ else
245
+ new_state.session_id = previous_state.session_id
246
+ new_state.state_id = previous_state.state_id
247
+ new_state.state_parent = previous_state.state_parent
248
+ else
249
+ new_state.session_id = last_committed_state.session_id
250
+ new_state.state_id = last_committed_state.state_id
251
+ new_state.state_parent = last_committed_state.state_parent
252
+ new_state.state_committed = last_committed_state.state_committed
253
+
254
+ @filter(new_state, previous_state) if @filter?
255
+ @state_raw = new_state
256
+ @state = @cast(JSON.parse(JSON.stringify(@state_raw)))
257
+ @state_valid = true
258
+
259
+ if @state_raw.state_committed
260
+ clearTimeout(commit_timer) if commit_timer
261
+ commit_scheduled_at = null
262
+ commit_timer = null
263
+ else
264
+ if commit_needed_at <= Date.now()
265
+ state_commit(replace)
266
+ else
267
+ location_update(false)
268
+ if (not commit_scheduled_at) or (commit_needed_at < commit_scheduled_at)
269
+ console.log("ROUTER: Deferring commit by:", defer, "till", commit_needed_at) if @debug
270
+ @state_raw.state_committed = false
271
+ clearTimeout(commit_timer) if commit_timer
272
+ commit_scheduled_at = commit_needed_at
273
+ commit_timer = setTimeout((()=> state_commit(replace)), commit_scheduled_at - Date.now())
274
+
275
+ @onchange(@state_raw,previous_state) if @onchange?
276
+
277
+
278
+ state_get_as_state_description = (state)=>
279
+ last_committed_state = state
280
+ while last_committed_state.state_parent and ((not last_committed_state.session_id) or last_committed_state.session_id == @session_id) and ((session_data_saved.object.max_state_id or 0 ) < last_committed_state.state_id)
281
+ last_committed_state = last_committed_state.state_parent
282
+ console.log('ROUTER: Last shareable state:', last_committed_state) if @debug
283
+ buffer = state_diff_generate(state_permanent(last_committed_state), state_permanent(state))
284
+ exposed = ([component[1], pair[1]] for pair in state_diff_generate({}, state) when component = _.find @expose, (component)-> component[0] == pair[0])
285
+ buffer = (pair for pair in buffer when not _.find @expose, (component)-> component[0] == pair[0])
286
+ { session_id: last_committed_state.session_id, state_id: last_committed_state.state_id, exposed: exposed, buffer: buffer, change: [] }
287
+
288
+
289
+ location_update = (new_history_entry)=>
290
+ url = state_description_to_url(state_get_as_state_description(@state_raw))
291
+ console.log("ROUTER: Updating location: state:", @state_raw, ' url:', url) if @debug
292
+ if new_history_entry
293
+ window.history.pushState(@state_raw,null,url)
294
+ else
295
+ window.history.replaceState(@state_raw,null,url)
296
+
297
+
298
+ @navigate = (search, options = {})->
299
+ pathname = window.location.pathname
300
+
301
+ console.log('ROUTER: Navigating to: pathname:', pathname, ' search:', search) if @debug
302
+ state_description = url_to_state_description(pathname, search)
303
+ console.log('ROUTER: New state description:', state_description) if @debug
304
+
305
+ if remote_state?
306
+ remote_state.unlink()
307
+ remote_state = null
308
+
309
+ if state_description.session_id == @session_id or state_description.state_id == "0"
310
+ state_set_from_state_description(state_description, (options.defer or 0), !options.replace)
311
+ else
312
+ @state_valid = false
313
+ remote_state = @seapig_client.slave('SeapigRouter::Session::'+state_description.session_id+'::State::'+state_description.state_id)
314
+ remote_state.onchange ()=>
315
+ return if not remote_state.valid
316
+ console.log("ROUTER: Received remote state", remote_state.object) if @debug
317
+ @state_raw = JSON.parse(JSON.stringify(remote_state.object))
318
+ @state_raw.state_committed = true
319
+ @state_raw.session_id = state_description.session_id
320
+ @state_raw.state_id = state_description.state_id
321
+ @state_raw.state_parent = undefined
322
+ state_set_from_state_description(state_description, (options.defer or 0), !options.replace)
323
+ remote_state.unlink()
324
+ remote_state = null
325
+
326
+
327
+ @volatile = (data...)->
328
+ if data.length == 1 and typeof data[0] == 'object'
329
+ for key, value of data[0]
330
+ @state["_"+key] = value
331
+ _.extend(@state_raw, _.pick(@state, (value,key)-> key.indexOf("_") == 0))
332
+ window.history.replaceState(@state_raw,null,window.location)
333
+ else
334
+ _.object(([key, @state["_"+key]] for key in data))
@@ -0,0 +1,5 @@
1
+ class SeapigRouterSession < ActiveRecord::Base
2
+
3
+ has_many :seapig_router_session_states
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ class SeapigRouterSessionState < ActiveRecord::Base
2
+
3
+ belongs_to :seapig_router_session
4
+
5
+ end
@@ -0,0 +1,55 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'slop'
4
+ require 'yaml'
5
+ require 'seapig-client'
6
+ require 'active_record'
7
+
8
+ require 'seapig-postgresql-notifier'
9
+ require 'seapig-router'
10
+
11
+
12
+
13
+ OPTIONS = Slop.parse { |o|
14
+ o.string '-c', '--connect', "Seapig server address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001"
15
+ o.string '-d', '--database-url', 'Database URL (e.g. postgres://USER:PASS@PGHOST/DBNAME)'
16
+ o.string '-e', '--environment' , 'Rails environment to use when loading database config from config/database.yml'
17
+ o.on '-h', '--help' do puts o; exit end
18
+ }
19
+
20
+ if (not OPTIONS["database-url"]) and (not File.exist?("config/database.yml")) then puts "Either -d or config/database.yml is needed"; exit end
21
+ database_config = (OPTIONS["database-url"] or YAML.load_file("config/database.yml")[(OPTIONS["environment"] or ENV["RAILS_ENV"] or "development")])
22
+ ActiveRecord::Base.establish_connection(database_config)
23
+
24
+
25
+ EM.run {
26
+
27
+ SeapigClient.new(OPTIONS["connect"],name: 'session-manager').slave('SeapigRouter::Session::*::Data').onchange { |session_data|
28
+ token = session_data["token"]
29
+ next if token != session_data.id.split('::')[2]
30
+ if not session = SeapigRouterSession.find_by(token: token)
31
+ begin
32
+ session_id = (('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).shuffle[0..11].join('')
33
+ session = SeapigRouterSession.create!(key: session_id, token: token)
34
+ rescue ActiveRecord::RecordNotUnique
35
+ retry #FIXME: DOS
36
+ end
37
+ puts "Created new session: "+session.key+" for token: "+token
38
+ end
39
+ next if session_data["session_id"] and session.key != session_data["session_id"]
40
+
41
+ print "Saving session "+session.key+" states: "
42
+ max_state = session.seapig_router_session_states.order("state_id DESC").first
43
+ max_state_id = (max_state and max_state.state_id or -1)
44
+ session_data['states'].each_pair { |id, state|
45
+ if id.to_i > max_state_id
46
+ print ' '+id
47
+ SeapigRouterSessionState.create!(seapig_router_session_id: session.id, state_id: id, state: state)
48
+ end
49
+ }
50
+ puts
51
+ SeapigDependency.bump("SeapigRouter::Session::"+token)
52
+ SeapigDependency.bump("SeapigRouter::Session::"+session.key)
53
+ }
54
+
55
+ }
@@ -0,0 +1,9 @@
1
+ class CreateSeapigRouterSessions < ActiveRecord::Migration
2
+ def change
3
+ create_table :seapig_router_sessions do |t|
4
+ t.text :key
5
+ t.timestamps null: false
6
+ end
7
+ add_index :seapig_router_sessions, :key, unique: true
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class CreateSeapigRouterSessionStates < ActiveRecord::Migration
2
+ def change
3
+ create_table :seapig_router_session_states do |t|
4
+ t.integer :seapig_router_session_id
5
+ t.integer :state_id
6
+ t.jsonb :state
7
+
8
+ t.timestamps null: false
9
+ end
10
+ add_index :seapig_router_session_states, [:seapig_router_session_id,:state_id], unique: true, name: "seapig_router_session_states_index_1"
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class AddTokenToSeapigSessions < ActiveRecord::Migration
2
+ def change
3
+ add_column :seapig_router_sessions, :token, :text
4
+ add_index :seapig_router_sessions, :token, unique: true, name: "seapig_router_sessions_token_index"
5
+ add_index :seapig_router_sessions, [:key,:token], unique: true, name: "seapig_router_sessions_key_token_index"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ require "seapig-router/engine"
2
+ require_relative '../app/models/seapig_router_session.rb'
3
+ require_relative '../app/models/seapig_router_session_state.rb'
4
+
5
+ module SeapigRouter
6
+ end
@@ -0,0 +1,6 @@
1
+ if defined? Rails
2
+ module SeapigRouter
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module SeapigRouter
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ class SeapigRouterSessionSaved < Producer
2
+
3
+ @patterns = [ 'SeapigRouter::Session::*::Saved' ]
4
+
5
+
6
+ def self.produce(seapig_object_id)
7
+ seapig_object_id =~ /SeapigRouter::Session::([^\:]+)::Saved/
8
+ token = $1
9
+ version = SeapigDependency.versions('SeapigRouter::Session::'+token)
10
+ session = SeapigRouterSession.find_by(token: token)
11
+ return [false, version] if not session
12
+ max_state = session.seapig_router_session_states.select("state_id").order("state_id DESC").first
13
+ data = {
14
+ session_id: session.key,
15
+ max_state_id: (max_state and max_state.state_id or -1)
16
+ }
17
+ [data, version]
18
+ end
19
+
20
+ end
@@ -0,0 +1,19 @@
1
+ class SeapigRouterSessionStateProducer < Producer
2
+
3
+ @patterns = [ 'SeapigRouter::Session::*::State::*' ]
4
+
5
+
6
+ def self.produce(seapig_object_id)
7
+ seapig_object_id =~ /SeapigRouter::Session::([^\:]+)::State::([^\:]+)/
8
+ session_key = $1
9
+ state_id = $2.to_i
10
+ version = Time.new.to_f
11
+ session = SeapigRouterSession.find_by(key: session_key)
12
+ return [false, SeapigDependency.versions('SeapigRouter::Session::'+session_key)] if not session
13
+ state = SeapigRouterSessionState.find_by(seapig_router_session_id: session.id, state_id: state_id)
14
+ return [false, SeapigDependency.versions('SeapigRouter::Session::'+session_key)] if not state
15
+ data = state.state
16
+ [data, version]
17
+ end
18
+
19
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seapig-router
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - yunta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: seapig-client-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: seapig-postgresql-notifier
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: meh
84
+ email:
85
+ - maciej.blomberg@mikoton.com
86
+ executables:
87
+ - seapig-router-session-manager
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - MIT-LICENSE
92
+ - Rakefile
93
+ - app/assets/javascripts/seapig/seapig-router.js.coffee
94
+ - app/models/seapig_router_session.rb
95
+ - app/models/seapig_router_session_state.rb
96
+ - bin/seapig-router-session-manager
97
+ - db/migrate/20151221110834_create_seapig_router_sessions.rb
98
+ - db/migrate/20151221111628_create_seapig_router_session_states.rb
99
+ - db/migrate/20161231183822_add_token_to_seapig_sessions.rb
100
+ - lib/seapig-router.rb
101
+ - lib/seapig-router/engine.rb
102
+ - lib/seapig-router/version.rb
103
+ - lib/seapigs/seapig_router_saved_session.rb
104
+ - lib/seapigs/seapig_router_session_state.rb
105
+ homepage: https://github.com/yunta-mb/seapig-rails
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.5.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Transient object synchronization lib - rails
129
+ test_files: []