statebus 6.1.20 → 7.0.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.
@@ -2,6 +2,7 @@
2
2
  var websocket_prefix = (clientjs_option('websocket_path')
3
3
  || '_connect_to_statebus_')
4
4
 
5
+ // Todo: remove this global
5
6
  window.dom = window.dom || new Proxy({}, {
6
7
  get: function (o, k) { return o[k] },
7
8
  set: function (o, k, v) {
@@ -37,12 +38,12 @@
37
38
  // link.href = url
38
39
  // url = link.href
39
40
  // }
40
- console.log('opening websocket to', url)
41
+
41
42
  return new WebSocket(url + '/' + websocket_prefix + '/websocket')
42
43
  // return new SockJS(url + '/' + websocket_prefix)
43
44
  }
44
45
  function client_creds (server_url) {
45
- var me = bus.fetch('ls/me')
46
+ var me = bus.get('ls/me')
46
47
  bus.log('connect: me is', me)
47
48
  if (!me.client) {
48
49
  // Create a client id if we have none yet.
@@ -51,19 +52,20 @@
51
52
  me.client = c || (Math.random().toString(36).substring(2)
52
53
  + Math.random().toString(36).substring(2)
53
54
  + Math.random().toString(36).substring(2))
54
- bus.save(me)
55
+ bus.set(me)
55
56
  }
56
57
 
57
58
  set_cookie('client', me.client)
58
59
  return {clientid: me.client}
59
60
  }
60
61
 
61
- function braid_http_mount (prefix, url) {
62
+ function http_mount (prefix, url) {
62
63
  var preprefix = prefix.slice(0,-1)
63
64
  var has_prefix = new RegExp('^' + preprefix)
64
65
  var is_absolute = /^https?:\/\//
65
- var client_fetched_keys = new bus.Set()
66
- //if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
66
+ var subscriptions = {}
67
+ var put_counter = 0
68
+
67
69
  function add_prefix (key) {
68
70
  return is_absolute.test(key) ? key : preprefix + key }
69
71
  function rem_prefix (key) {
@@ -73,45 +75,146 @@
73
75
  function rem_prefixes (obj) {
74
76
  return bus.translate_keys(bus.clone(obj), rem_prefix) }
75
77
 
76
- bus(prefix).to_save = function (obj, t) {
77
- bus.save.fire(obj)
78
+ var puts = new Map()
79
+ function enqueue_put (url, body) {
80
+ var id = put_counter++
81
+ puts.set(id, {url: url, body: body, id: id})
82
+ send_put(id)
83
+ }
84
+ function send_put (id) {
85
+ try {
86
+ puts.get(id).status = 'sending'
87
+ braid_fetch(
88
+ puts.get(id).url,
89
+ {
90
+ method: 'put',
91
+ headers: {
92
+ 'content-type': 'application/json',
93
+ 'put-order': id,
94
+ },
95
+ body: puts.get(id).body
96
+ }
97
+ ).then(function (res) {
98
+ if (res.status !== 200)
99
+ console.error('Server gave error on PUT:',
100
+ e, 'for', puts.get(id).body)
101
+ puts.delete(id)
102
+ }).catch(function (e) {
103
+ console.error('Error on PUT, waiting...', puts.get(id).url)
104
+ puts.get(id).status = 'waiting'
105
+ })
106
+ } catch (e) {
107
+ console.error('Error on PUT, waiting...', puts.get(id).url)
108
+ puts.get(id).status = 'waiting'
109
+ }
110
+ }
111
+ function retry_put (id) {
112
+ setTimeout(function () {send_put(id)}, 1000)
113
+ }
114
+ function send_all_puts () {
115
+ for (var id of puts.keys())
116
+ if (puts.get(id).status === 'waiting') {
117
+ console.log('Sending waiting put', id)
118
+ send_put(id)
119
+ }
120
+ }
78
121
 
79
- // var x = {save: obj}
80
- // if (t.version) x.version = t.version
81
- // if (t.parents) x.parents = t.parents
82
- // if (t.patch) x.patch = t.patch
83
- // if (t.patch) x.save = rem_prefix(x.save.key)
122
+ bus(prefix).to_set = function (obj, t) {
123
+ bus.set.fire(obj)
84
124
 
85
- braid_fetch(url + rem_prefix(obj.key),
86
- {
87
- method: 'put',
88
- 'content-type': 'application/json',
89
- body: JSON.stringify(obj.val)
90
- })
125
+ var put = {
126
+ url: url + rem_prefix(obj.key),
127
+ body: JSON.stringify(obj.val)
128
+ }
129
+ if (t.version) put.version = t.version
130
+ if (t.parents) put.parents = t.parents
131
+ if (t.patch) put.patch = t.patch
132
+ var put_id = put_counter++
133
+ puts.set(put_id, put)
134
+ send_put(put_id)
91
135
  }
92
136
 
93
- bus(prefix).to_fetch = function (key, t) {
94
- try {
95
- braid_fetch(url + rem_prefix(key),
96
- {
97
- method: 'get',
98
- subscribe: true,
99
- headers: {accept: 'application/json'}
100
- }
101
- ).andThen( function (x) {
102
- t.return({
103
- key: key,
104
- val: add_prefixes(JSON.parse(x.body))
105
- })
106
- })
107
- } catch (e) {
108
- console.error('Braid-HTTP network error:', e)
137
+ bus(prefix).to_get = function (key, t) {
138
+ // Subscription can be in states:
139
+ // - connecting
140
+ // - connected
141
+ // - reconnect
142
+ // - reconnecting
143
+ // - aborted
144
+
145
+ // If we have an outstanding get running, then let's tell it to
146
+ // re-activate!
147
+ if (subscriptions[key]) {
148
+ // We should only be here if an existing subscription was
149
+ // aborted, but hasn't cleared yet.
150
+ console.assert(subscriptions[key].status === 'aborted',
151
+ 'Regetting a subscription of status '
152
+ + subscriptions[key].status)
153
+
154
+ console.trace('foo')
155
+
156
+ // Let's tell it to reconnect when it tries to clear!
157
+ subscriptions[key].status = 'reconnect'
158
+ }
159
+
160
+ // Otherwise, create a new subscription
161
+ else
162
+ subscribe (key, t)
163
+
164
+ function subscribe (key, t) {
165
+ var aborter = new AbortController(),
166
+ reconnect_attempts = 0
167
+
168
+ // Start the subscription!
169
+ braid_fetch(
170
+ // URL
171
+ url + rem_prefix(key),
172
+
173
+ // Options
174
+ {
175
+ method: 'get',
176
+ subscribe: true,
177
+ headers: {accept: 'application/json'},
178
+ signal: aborter.signal
179
+ }
180
+ ).andThen( function (x) {
181
+ // New update received!
182
+ if (subscriptions[key].status === 'connecting') {
183
+ console.log('%c[*] opened ' + key,
184
+ 'color: blue')
185
+ reconnect_attempts = 0
186
+ subscriptions[key].status = 'connected'
187
+ send_all_puts()
188
+ }
189
+
190
+ // Return the update
191
+ t.return({
192
+ key: key,
193
+ val: add_prefixes(JSON.parse(x.body))
194
+ })
195
+ }).catch( function (e) {
196
+ if (subscriptions[key].status === 'aborted') {
197
+ // Then this get is over and done with!
198
+ delete subscriptions[key]
199
+ return
200
+ }
201
+
202
+ // Reconnect!
203
+ setTimeout(function () { subscribe(key, t) },
204
+ reconnect_attempts > 0 ? 5000 : 1500)
205
+ subscriptions[key].status = 'reconnecting'
206
+ })
207
+
208
+ // Remember this subscription
209
+ subscriptions[key] = {
210
+ aborter: aborter,
211
+ status: 'connecting'
212
+ }
109
213
  }
110
- client_fetched_keys.add(key)
111
214
  }
112
215
  bus(prefix).to_forget = function (key) {
113
- // send({forget: key}),
114
- client_fetched_keys.delete(key)
216
+ subscriptions[key].status = 'aborted'
217
+ subscriptions[key].aborter.abort()
115
218
  }
116
219
  }
117
220
 
@@ -127,31 +230,31 @@
127
230
  var bus = this
128
231
  bus.log(this)
129
232
 
130
- // Fetch returns the value immediately in a save
131
- // Saves are queued up, to store values with a delay, in batch
132
- var saves_are_pending = false
133
- var pending_saves = {}
233
+ // Get returns the value immediately in a set
234
+ // Sets are queued up, to store values with a delay, in batch
235
+ var sets_are_pending = false
236
+ var pending_sets = {}
134
237
 
135
- function save_the_pending_saves() {
136
- bus.log('localstore: saving', pending_saves)
137
- for (var k in pending_saves)
138
- localStorage.setItem(k, JSON.stringify(pending_saves[k]))
139
- saves_are_pending = false
238
+ function set_the_pending_sets() {
239
+ bus.log('localstore: saving', pending_sets)
240
+ for (var k in pending_sets)
241
+ localStorage.setItem(k, JSON.stringify(pending_sets[k]))
242
+ sets_are_pending = false
140
243
  }
141
244
 
142
- bus(prefix).to_fetch = function (key) {
245
+ bus(prefix).to_get = function (key) {
143
246
  var result = localStorage.getItem(key)
144
247
  return result ? JSON.parse(result) : {key: key}
145
248
  }
146
- bus(prefix).to_save = function (obj) {
249
+ bus(prefix).to_set = function (obj) {
147
250
  // Do I need to make this recurse into the object?
148
- bus.log('localStore: on_save:', obj.key)
149
- pending_saves[obj.key] = obj
150
- if (!saves_are_pending) {
151
- setTimeout(save_the_pending_saves, 50)
152
- saves_are_pending = true
251
+ bus.log('localStore: on_set:', obj.key)
252
+ pending_sets[obj.key] = obj
253
+ if (!sets_are_pending) {
254
+ setTimeout(set_the_pending_sets, 50)
255
+ sets_are_pending = true
153
256
  }
154
- bus.save.fire(obj)
257
+ bus.set.fire(obj)
155
258
  return obj
156
259
  }
157
260
  bus(prefix).to_delete = function (key) { localStorage.removeItem(key) }
@@ -160,6 +263,7 @@
160
263
  // Hm... this update stuff doesn't seem to work on file:/// urls in chrome
161
264
  function update (event) {
162
265
  bus.log('Got a localstorage update', event)
266
+ bus.dirty(event.key)
163
267
  //this.get(event.key.substr('statebus '.length))
164
268
  }
165
269
  if (window.addEventListener) window.addEventListener("storage", update, false)
@@ -184,39 +288,16 @@
184
288
  data = (data && JSON.parse(data)) || {key : key}
185
289
  // Then I would need to:
186
290
  // - Change the key prefix
187
- // - Save this into the cache
291
+ // - Set this into the cache
188
292
 
189
- bus(prefix).to_save = function (obj) {
293
+ bus(prefix).to_set = function (obj) {
190
294
  window.history.replaceState(
191
295
  '',
192
296
  '',
193
297
  document.location.origin
194
298
  + document.location.pathname
195
299
  + escape('?'+key+'='+JSON.stringify(obj)))
196
- bus.save.fire(obj)
197
- }
198
- }
199
-
200
- function live_reload_from (prefix) {
201
- if (!window.live_reload_initialized) {
202
- var first_time = true
203
- this(function () {
204
- var re = new RegExp(".*/" + prefix + "/(.*)")
205
- var file = window.location.href.match(re)[1]
206
- var code = bus.fetch('/code/invisible.college/' + file).code
207
- if (!code) return
208
- if (first_time) {first_time = false; return}
209
- var old_scroll_position = window.pageYOffset
210
- document.body.innerHTML = code
211
- var i = 0
212
- var d = 100
213
- var interval = setInterval(function () {
214
- if (i > 500) clearInterval(interval)
215
- i += d
216
- window.scrollTo(0, old_scroll_position)
217
- }, d)
218
- })
219
- window.live_reload_initialized = true
300
+ bus.set.fire(obj)
220
301
  }
221
302
  }
222
303
 
@@ -224,30 +305,40 @@
224
305
  // ****************
225
306
  // Wrapper for React Components
226
307
 
308
+ function react_version () {
309
+ if (!window.React) return undefined;
310
+ return Number(window.React.version.split('.')[0])
311
+ }
312
+
313
+ // Newer React requires createReactClass as a separate libary
314
+ if (window.React && !React.createClass && window.createReactClass)
315
+ React.createClass = createReactClass
316
+
227
317
  // XXX Currently assumes there's a statebus named "bus" in global
228
318
  // XXX scope.
229
319
 
230
320
  var components = {} // Indexed by 'component/0', 'component/1', etc.
231
321
  var components_count = 0
232
322
  var dirty_components = {}
233
- function React_View(component) {
323
+ function create_react_class(component) {
234
324
  function wrap(name, new_func) {
235
325
  var old_func = component[name]
236
326
  component[name] = function wrapper () { return new_func.bind(this)(old_func) }
237
327
  }
238
328
 
239
329
  // Register the component's basic info
240
- wrap('componentWillMount', function new_cwm (orig_func) {
241
- if (component.displayName === undefined)
242
- throw 'Component needs a displayName'
243
- this.name = component.displayName.toLowerCase().replace(' ', '_')
330
+ wrap((react_version() >= 16 ? 'UNSAFE_' : '') + 'componentWillMount',
331
+ function new_cwm (orig_func) {
332
+ // if (component.displayName === undefined)
333
+ // throw 'Component needs a displayName'
334
+ //this.name = component.displayName.toLowerCase().replace(' ', '_')
244
335
  this.key = 'component/' + components_count++
245
336
  components[this.key] = this
246
337
 
247
338
  function add_shortcut (obj, shortcut_name, to_key) {
248
339
  delete obj[shortcut_name]
249
340
  Object.defineProperty(obj, shortcut_name, {
250
- get: function () { return bus.fetch(to_key) },
341
+ get: function () { return bus.get(to_key) },
251
342
  configurable: true })
252
343
  }
253
344
  add_shortcut(this, 'local', this.key)
@@ -268,7 +359,7 @@
268
359
  && typeof this.props[k] === 'object'
269
360
  && this.props[k].key)
270
361
 
271
- bus.fetch(this.props[k].key)
362
+ bus.get(this.props[k].key)
272
363
 
273
364
  // Call the renderer!
274
365
  return orig_render.apply(this, arguments)
@@ -339,17 +430,12 @@
339
430
  props['data-key'] = props.key
340
431
  props['data-widget'] = component.displayName
341
432
 
342
- return (React.version >= '0.12.'
343
- ? React.createElement(react_class, props, children)
344
- : react_class(props, children))
433
+ return React.createElement(react_class, props, children)
345
434
  }
346
- // Give it the same prototype as the original class so that it
347
- // passes React.isValidClass() inspection
348
- result.prototype = react_class.prototype
435
+ Object.defineProperty(result, 'name',
436
+ {value: component.displayName, writable: false})
349
437
  return result
350
438
  }
351
- window.React_View = React_View
352
- if (window.statebus) window.statebus.create_react_class = window.statebus.createReactClass = React_View
353
439
 
354
440
  // *****************
355
441
  // Re-rendering react components
@@ -384,7 +470,7 @@
384
470
 
385
471
  function make_client_statebus_maker () {
386
472
  var extra_stuff = ['localstorage_client make_websocket client_creds',
387
- 'url_store components live_reload_from'].join(' ').split(' ')
473
+ 'url_store components'].join(' ').split(' ')
388
474
  if (window.statebus) {
389
475
  var orig_statebus = statebus
390
476
  window.statebus = function make_client_bus () {
@@ -405,17 +491,17 @@
405
491
  if (statebus_dir) statebus_dir = statebus_dir[1] + '/'
406
492
  else statebus_dir = ''
407
493
 
408
- var js_urls = {
409
- react: statebus_dir + 'extras/react.js',
410
- sockjs: statebus_dir + 'extras/sockjs.js',
411
- coffee: statebus_dir + 'extras/coffee.js',
412
- statebus: statebus_dir + 'statebus.js'
413
- }
414
- if (statebus_dir == 'https://stateb.us/')
415
- js_urls.statebus = statebus_dir + 'statebus4.js'
494
+ // var js_urls = {
495
+ // react: statebus_dir + 'extras/react.js',
496
+ // sockjs: statebus_dir + 'extras/sockjs.js',
497
+ // coffee: statebus_dir + 'extras/coffee.js',
498
+ // statebus: statebus_dir + 'statebus.js'
499
+ // }
500
+ // if (statebus_dir == 'https://stateb.us/')
501
+ // js_urls.statebus = statebus_dir + 'statebus4.js'
416
502
 
417
- for (var name in js_urls)
418
- document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
503
+ // for (var name in js_urls)
504
+ // document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
419
505
 
420
506
  document.addEventListener('DOMContentLoaded', scripts_ready, false)
421
507
  }
@@ -426,57 +512,50 @@
426
512
  function clientjs_option (option_name) {
427
513
  // This function must be copy/paste synchronized with statebus.js. Be
428
514
  // sure to clone all edits there.
429
- var script_elem = (
430
- document.querySelector('script[src*="/client"][src$=".js"]') ||
431
- document.querySelector('script[src^="client"][src$=".js"]'))
515
+ var script_elem = document.querySelector('script[src$="statebus/client.js"]')
432
516
  return script_elem && script_elem.getAttribute(option_name)
433
517
  }
434
518
  var loaded_from_file_url = window.location.href.match(/^file:\/\//)
435
- window.statebus_server = window.statebus_server || clientjs_option('server') ||
436
- (loaded_from_file_url ? 'https://stateb.us:3006' : '/')
437
- window.statebus_backdoor = window.statebus_backdoor || clientjs_option('backdoor')
519
+
520
+ // Todo: remove this global
521
+ window.statebus_server = clientjs_option('server')
438
522
  var react_render
439
523
  function scripts_ready () {
440
- react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
524
+ react_render = ReactDOM.render
441
525
  make_client_statebus_maker()
442
526
  window.bus = window.statebus()
443
527
  bus.label = 'bus'
444
- if (clientjs_option('braid_mode'))
445
- bus.state = bus.braid_proxy()
446
- else
447
- window.sb = bus.sb
448
- statebus.widget = React_View
449
- statebus.create_react_class = React_View
450
- statebus.createReactClass = React_View
451
-
452
- improve_react()
453
- window.ignore_flashbacks = false
454
- if (statebus_server !== 'none') {
455
- if (clientjs_option('braid_mode')) {
456
- console.log('We socket-free!')
457
- braid_http_mount ('/*', statebus_server)
458
- } else {
459
- bus.net_mount ('/*', statebus_server)
460
- console.log('We going websocket!')
461
- }
462
- }
463
528
 
464
- if (window.statebus_backdoor) {
465
- window.master = statebus()
466
- master.net_mount('*', statebus_backdoor)
467
- }
529
+ statebus.create_react_class = create_react_class
530
+ statebus.createReactClass = create_react_class
531
+
532
+ // improve_react()
533
+ statebus.ignore_flashbacks = false
534
+ bus.libs = {}
535
+ bus.libs.http_out = http_mount
536
+ bus.libs.react_class = create_react_class
537
+ bus.libs.input = make_better_input_17()
538
+ // if (statebus_server !== 'none') {
539
+ // if (clientjs_option('braid_mode')) {
540
+ // console.log('Using Braid-HTTP!')
541
+ // http_mount ('/*', statebus_server)
542
+ // } else {
543
+ // bus.ws_mount ('/*', statebus_server)
544
+ // }
545
+ // }
546
+
468
547
  bus.net_automount()
469
548
 
470
549
  // This /new/* code is deprecated
471
550
  if (!clientjs_option('braid_mode')) {
472
- bus('/new/*').to_save = function (o) {
551
+ bus('/new/*').to_set = function (o) {
473
552
  if (o.key.split('/').length > 3) return
474
553
 
475
554
  var old_key = o.key
476
555
  o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
477
556
  statebus.cache[o.key] = o
478
557
  delete statebus.cache[old_key]
479
- bus.save(o)
558
+ bus.set(o)
480
559
  }
481
560
  }
482
561
 
@@ -486,6 +565,17 @@
486
565
  statebus.load_client_code = load_client_code
487
566
  statebus.load_widgets = load_widgets
488
567
 
568
+ if (clientjs_option('globals')) {
569
+ // Setup globals
570
+ var globals = ['get', 'set', 'state']
571
+
572
+ for (var i=0; i<globals.length; i++) {
573
+ console.log('globalizing', globals[i], 'as',
574
+ eval('bus.' + globals[i]))
575
+ window[globals[i]] = eval('bus.' + globals[i])
576
+ }
577
+ }
578
+
489
579
  document.addEventListener('DOMContentLoaded', function () {
490
580
  if (window.statebus_ready)
491
581
  for (var i=0; i<statebus_ready.length; i++)
@@ -605,7 +695,7 @@
605
695
  for (var i=0; i<arguments.length; i++) {
606
696
  args.push(arguments[i])
607
697
  if (arguments[i].state)
608
- args[i].src = 'data:;base64,' + fetch(args[i].state)._
698
+ args[i].src = 'data:;base64,' + bus.get(args[i].state)._
609
699
  }
610
700
  return og_img.apply(this, args)
611
701
  }
@@ -637,12 +727,38 @@
637
727
  }
638
728
 
639
729
 
730
+ // This one is for react v17+
731
+ function make_better_input_17 () {
732
+ return createReactClass({
733
+ getInitialState: function() {
734
+ return {value: this.props.value}
735
+ },
736
+ UNSAVE_componentWillReceiveProps: function(new_props) {
737
+ this.setState({value: new_props.value})
738
+ },
739
+ onChange: function(e) {
740
+ this.props.onChange && this.props.onChange(e)
741
+ if (this.props.value)
742
+ this.setState({value: e.target.value})
743
+ },
744
+ render: function() {
745
+ var new_props = {}
746
+ for (var k in this.props)
747
+ if (this.props.hasOwnProperty(k))
748
+ new_props[k] = this.props[k]
749
+ if (this.state.value) new_props.value = this.state.value
750
+ new_props.onChange = this.onChange
751
+ return React.createElement('input', new_props)
752
+ }
753
+ })
754
+ }
755
+
640
756
  // Load the components
641
757
  var users_widgets = {}
642
758
  function make_component(name, func) {
643
759
  // Define the component
644
760
 
645
- window[name] = users_widgets[name] = window.React_View({
761
+ window[name] = users_widgets[name] = statebus.create_react_class({
646
762
  displayName: name,
647
763
  render: function () {
648
764
  var args = []