statebus 6.1.22 → 7.0.2

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) {
@@ -42,7 +43,7 @@
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
66
  var subscriptions = {}
66
- //if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
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,24 +75,66 @@
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) {
137
+ bus(prefix).to_get = function (key, t) {
94
138
  // Subscription can be in states:
95
139
  // - connecting
96
140
  // - connected
@@ -98,13 +142,13 @@
98
142
  // - reconnecting
99
143
  // - aborted
100
144
 
101
- // If we have an outstanding fetch running, then let's tell it to
145
+ // If we have an outstanding get running, then let's tell it to
102
146
  // re-activate!
103
147
  if (subscriptions[key]) {
104
148
  // We should only be here if an existing subscription was
105
149
  // aborted, but hasn't cleared yet.
106
150
  console.assert(subscriptions[key].status === 'aborted',
107
- 'Refetching a subscription of status '
151
+ 'Regetting a subscription of status '
108
152
  + subscriptions[key].status)
109
153
 
110
154
  console.trace('foo')
@@ -140,6 +184,7 @@
140
184
  'color: blue')
141
185
  reconnect_attempts = 0
142
186
  subscriptions[key].status = 'connected'
187
+ send_all_puts()
143
188
  }
144
189
 
145
190
  // Return the update
@@ -149,7 +194,7 @@
149
194
  })
150
195
  }).catch( function (e) {
151
196
  if (subscriptions[key].status === 'aborted') {
152
- // Then this fetch is over and done with!
197
+ // Then this get is over and done with!
153
198
  delete subscriptions[key]
154
199
  return
155
200
  }
@@ -179,37 +224,30 @@
179
224
  function localstorage_client (prefix) {
180
225
  try { localStorage } catch (e) { return }
181
226
 
182
- // This doesn't yet trigger updates across multiple browser windows.
183
- // We can do that by adding a list of dirty keys and
184
-
185
- var bus = this
186
- bus.log(this)
227
+ // Sets are queued up, to store values with a delay, in batch
228
+ var sets_are_pending = false
229
+ var pending_sets = {}
187
230
 
188
- // Fetch returns the value immediately in a save
189
- // Saves are queued up, to store values with a delay, in batch
190
- var saves_are_pending = false
191
- var pending_saves = {}
192
-
193
- function save_the_pending_saves() {
194
- bus.log('localstore: saving', pending_saves)
195
- for (var k in pending_saves)
196
- localStorage.setItem(k, JSON.stringify(pending_saves[k]))
197
- saves_are_pending = false
231
+ function set_the_pending_sets() {
232
+ bus.log('localstore: saving', pending_sets)
233
+ for (var k in pending_sets)
234
+ localStorage.setItem(k, JSON.stringify(pending_sets[k]))
235
+ sets_are_pending = false
198
236
  }
199
237
 
200
- bus(prefix).to_fetch = function (key) {
238
+ bus(prefix).to_get = function (key) {
201
239
  var result = localStorage.getItem(key)
202
240
  return result ? JSON.parse(result) : {key: key}
203
241
  }
204
- bus(prefix).to_save = function (obj) {
242
+ bus(prefix).to_set = function (obj) {
205
243
  // Do I need to make this recurse into the object?
206
- bus.log('localStore: on_save:', obj.key)
207
- pending_saves[obj.key] = obj
208
- if (!saves_are_pending) {
209
- setTimeout(save_the_pending_saves, 50)
210
- saves_are_pending = true
244
+ bus.log('localStore: on_set:', obj.key)
245
+ pending_sets[obj.key] = obj
246
+ if (!sets_are_pending) {
247
+ setTimeout(set_the_pending_sets, 50)
248
+ sets_are_pending = true
211
249
  }
212
- bus.save.fire(obj)
250
+ bus.set.fire(obj)
213
251
  return obj
214
252
  }
215
253
  bus(prefix).to_delete = function (key) { localStorage.removeItem(key) }
@@ -218,6 +256,7 @@
218
256
  // Hm... this update stuff doesn't seem to work on file:/// urls in chrome
219
257
  function update (event) {
220
258
  bus.log('Got a localstorage update', event)
259
+ bus.dirty(event.key)
221
260
  //this.get(event.key.substr('statebus '.length))
222
261
  }
223
262
  if (window.addEventListener) window.addEventListener("storage", update, false)
@@ -242,39 +281,16 @@
242
281
  data = (data && JSON.parse(data)) || {key : key}
243
282
  // Then I would need to:
244
283
  // - Change the key prefix
245
- // - Save this into the cache
284
+ // - Set this into the cache
246
285
 
247
- bus(prefix).to_save = function (obj) {
286
+ bus(prefix).to_set = function (obj) {
248
287
  window.history.replaceState(
249
288
  '',
250
289
  '',
251
290
  document.location.origin
252
291
  + document.location.pathname
253
292
  + escape('?'+key+'='+JSON.stringify(obj)))
254
- bus.save.fire(obj)
255
- }
256
- }
257
-
258
- function live_reload_from (prefix) {
259
- if (!window.live_reload_initialized) {
260
- var first_time = true
261
- this(function () {
262
- var re = new RegExp(".*/" + prefix + "/(.*)")
263
- var file = window.location.href.match(re)[1]
264
- var code = bus.fetch('/code/invisible.college/' + file).code
265
- if (!code) return
266
- if (first_time) {first_time = false; return}
267
- var old_scroll_position = window.pageYOffset
268
- document.body.innerHTML = code
269
- var i = 0
270
- var d = 100
271
- var interval = setInterval(function () {
272
- if (i > 500) clearInterval(interval)
273
- i += d
274
- window.scrollTo(0, old_scroll_position)
275
- }, d)
276
- })
277
- window.live_reload_initialized = true
293
+ bus.set.fire(obj)
278
294
  }
279
295
  }
280
296
 
@@ -282,30 +298,40 @@
282
298
  // ****************
283
299
  // Wrapper for React Components
284
300
 
301
+ function react_version () {
302
+ if (!window.React) return undefined;
303
+ return Number(window.React.version.split('.')[0])
304
+ }
305
+
306
+ // Newer React requires createReactClass as a separate libary
307
+ if (window.React && !React.createClass && window.createReactClass)
308
+ React.createClass = createReactClass
309
+
285
310
  // XXX Currently assumes there's a statebus named "bus" in global
286
311
  // XXX scope.
287
312
 
288
313
  var components = {} // Indexed by 'component/0', 'component/1', etc.
289
314
  var components_count = 0
290
315
  var dirty_components = {}
291
- function React_View(component) {
316
+ function create_react_class(component) {
292
317
  function wrap(name, new_func) {
293
318
  var old_func = component[name]
294
319
  component[name] = function wrapper () { return new_func.bind(this)(old_func) }
295
320
  }
296
321
 
297
322
  // Register the component's basic info
298
- wrap('componentWillMount', function new_cwm (orig_func) {
299
- if (component.displayName === undefined)
300
- throw 'Component needs a displayName'
301
- this.name = component.displayName.toLowerCase().replace(' ', '_')
323
+ wrap((react_version() >= 16 ? 'UNSAFE_' : '') + 'componentWillMount',
324
+ function new_cwm (orig_func) {
325
+ // if (component.displayName === undefined)
326
+ // throw 'Component needs a displayName'
327
+ //this.name = component.displayName.toLowerCase().replace(' ', '_')
302
328
  this.key = 'component/' + components_count++
303
329
  components[this.key] = this
304
330
 
305
331
  function add_shortcut (obj, shortcut_name, to_key) {
306
332
  delete obj[shortcut_name]
307
333
  Object.defineProperty(obj, shortcut_name, {
308
- get: function () { return bus.fetch(to_key) },
334
+ get: function () { return bus.get(to_key) },
309
335
  configurable: true })
310
336
  }
311
337
  add_shortcut(this, 'local', this.key)
@@ -326,7 +352,7 @@
326
352
  && typeof this.props[k] === 'object'
327
353
  && this.props[k].key)
328
354
 
329
- bus.fetch(this.props[k].key)
355
+ bus.get(this.props[k].key)
330
356
 
331
357
  // Call the renderer!
332
358
  return orig_render.apply(this, arguments)
@@ -397,17 +423,12 @@
397
423
  props['data-key'] = props.key
398
424
  props['data-widget'] = component.displayName
399
425
 
400
- return (React.version >= '0.12.'
401
- ? React.createElement(react_class, props, children)
402
- : react_class(props, children))
426
+ return React.createElement(react_class, props, children)
403
427
  }
404
- // Give it the same prototype as the original class so that it
405
- // passes React.isValidClass() inspection
406
- result.prototype = react_class.prototype
428
+ Object.defineProperty(result, 'name',
429
+ {value: component.displayName, writable: false})
407
430
  return result
408
431
  }
409
- window.React_View = React_View
410
- if (window.statebus) window.statebus.create_react_class = window.statebus.createReactClass = React_View
411
432
 
412
433
  // *****************
413
434
  // Re-rendering react components
@@ -442,7 +463,7 @@
442
463
 
443
464
  function make_client_statebus_maker () {
444
465
  var extra_stuff = ['localstorage_client make_websocket client_creds',
445
- 'url_store components live_reload_from'].join(' ').split(' ')
466
+ 'url_store components'].join(' ').split(' ')
446
467
  if (window.statebus) {
447
468
  var orig_statebus = statebus
448
469
  window.statebus = function make_client_bus () {
@@ -463,17 +484,17 @@
463
484
  if (statebus_dir) statebus_dir = statebus_dir[1] + '/'
464
485
  else statebus_dir = ''
465
486
 
466
- var js_urls = {
467
- react: statebus_dir + 'extras/react.js',
468
- sockjs: statebus_dir + 'extras/sockjs.js',
469
- coffee: statebus_dir + 'extras/coffee.js',
470
- statebus: statebus_dir + 'statebus.js'
471
- }
472
- if (statebus_dir == 'https://stateb.us/')
473
- js_urls.statebus = statebus_dir + 'statebus4.js'
487
+ // var js_urls = {
488
+ // react: statebus_dir + 'extras/react.js',
489
+ // sockjs: statebus_dir + 'extras/sockjs.js',
490
+ // coffee: statebus_dir + 'extras/coffee.js',
491
+ // statebus: statebus_dir + 'statebus.js'
492
+ // }
493
+ // if (statebus_dir == 'https://stateb.us/')
494
+ // js_urls.statebus = statebus_dir + 'statebus4.js'
474
495
 
475
- for (var name in js_urls)
476
- document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
496
+ // for (var name in js_urls)
497
+ // document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
477
498
 
478
499
  document.addEventListener('DOMContentLoaded', scripts_ready, false)
479
500
  }
@@ -484,56 +505,52 @@
484
505
  function clientjs_option (option_name) {
485
506
  // This function must be copy/paste synchronized with statebus.js. Be
486
507
  // sure to clone all edits there.
487
- var script_elem = (
488
- document.querySelector('script[src*="/client"][src$=".js"]') ||
489
- document.querySelector('script[src^="client"][src$=".js"]'))
508
+ var script_elem = document.querySelector('script[src$="statebus/client.js"]')
490
509
  return script_elem && script_elem.getAttribute(option_name)
491
510
  }
492
511
  var loaded_from_file_url = window.location.href.match(/^file:\/\//)
493
- window.statebus_server = window.statebus_server || clientjs_option('server') ||
494
- (loaded_from_file_url ? 'https://stateb.us:3006' : '/')
495
- window.statebus_backdoor = window.statebus_backdoor || clientjs_option('backdoor')
512
+
513
+ // Todo: remove this global
514
+ window.statebus_server = clientjs_option('server')
496
515
  var react_render
497
516
  function scripts_ready () {
498
- react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
499
- make_client_statebus_maker()
517
+ react_render = ReactDOM.render
518
+ // make_client_statebus_maker()
500
519
  window.bus = window.statebus()
501
520
  bus.label = 'bus'
502
- if (clientjs_option('braid_mode'))
503
- bus.state = bus.braid_proxy()
504
- else
505
- window.sb = bus.sb
506
- statebus.widget = React_View
507
- statebus.create_react_class = React_View
508
- statebus.createReactClass = React_View
509
-
510
- improve_react()
511
- window.ignore_flashbacks = false
512
- if (statebus_server !== 'none') {
513
- if (clientjs_option('braid_mode')) {
514
- console.log('Using Braid-HTTP!')
515
- braid_http_mount ('/*', statebus_server)
516
- } else {
517
- bus.net_mount ('/*', statebus_server)
518
- }
519
- }
520
521
 
521
- if (window.statebus_backdoor) {
522
- window.master = statebus()
523
- master.net_mount('*', statebus_backdoor)
524
- }
522
+ statebus.create_react_class = create_react_class
523
+ statebus.createReactClass = create_react_class
524
+
525
+ // improve_react()
526
+ statebus.ignore_flashbacks = false
527
+ bus.libs = {}
528
+ bus.libs.http_out = http_mount
529
+ bus.libs.react_class = create_react_class
530
+ bus.libs.input = make_better_input_17()
531
+ bus.libs.localstorage = localstorage_client
532
+
533
+ // if (statebus_server !== 'none') {
534
+ // if (clientjs_option('braid_mode')) {
535
+ // console.log('Using Braid-HTTP!')
536
+ // http_mount ('/*', statebus_server)
537
+ // } else {
538
+ // bus.ws_mount ('/*', statebus_server)
539
+ // }
540
+ // }
541
+
525
542
  bus.net_automount()
526
543
 
527
544
  // This /new/* code is deprecated
528
545
  if (!clientjs_option('braid_mode')) {
529
- bus('/new/*').to_save = function (o) {
546
+ bus('/new/*').to_set = function (o) {
530
547
  if (o.key.split('/').length > 3) return
531
548
 
532
549
  var old_key = o.key
533
550
  o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
534
551
  statebus.cache[o.key] = o
535
552
  delete statebus.cache[old_key]
536
- bus.save(o)
553
+ bus.set(o)
537
554
  }
538
555
  }
539
556
 
@@ -543,6 +560,17 @@
543
560
  statebus.load_client_code = load_client_code
544
561
  statebus.load_widgets = load_widgets
545
562
 
563
+ if (clientjs_option('globals')) {
564
+ // Setup globals
565
+ var globals = ['get', 'set', 'state']
566
+
567
+ for (var i=0; i<globals.length; i++) {
568
+ console.log('globalizing', globals[i], 'as',
569
+ eval('bus.' + globals[i]))
570
+ window[globals[i]] = eval('bus.' + globals[i])
571
+ }
572
+ }
573
+
546
574
  document.addEventListener('DOMContentLoaded', function () {
547
575
  if (window.statebus_ready)
548
576
  for (var i=0; i<statebus_ready.length; i++)
@@ -662,7 +690,7 @@
662
690
  for (var i=0; i<arguments.length; i++) {
663
691
  args.push(arguments[i])
664
692
  if (arguments[i].state)
665
- args[i].src = 'data:;base64,' + fetch(args[i].state)._
693
+ args[i].src = 'data:;base64,' + bus.get(args[i].state)._
666
694
  }
667
695
  return og_img.apply(this, args)
668
696
  }
@@ -694,12 +722,38 @@
694
722
  }
695
723
 
696
724
 
725
+ // This one is for react v17+
726
+ function make_better_input_17 () {
727
+ return createReactClass({
728
+ getInitialState: function() {
729
+ return {value: this.props.value}
730
+ },
731
+ UNSAVE_componentWillReceiveProps: function(new_props) {
732
+ this.setState({value: new_props.value})
733
+ },
734
+ onChange: function(e) {
735
+ this.props.onChange && this.props.onChange(e)
736
+ if (this.props.value)
737
+ this.setState({value: e.target.value})
738
+ },
739
+ render: function() {
740
+ var new_props = {}
741
+ for (var k in this.props)
742
+ if (this.props.hasOwnProperty(k))
743
+ new_props[k] = this.props[k]
744
+ if (this.state.value) new_props.value = this.state.value
745
+ new_props.onChange = this.onChange
746
+ return React.createElement('input', new_props)
747
+ }
748
+ })
749
+ }
750
+
697
751
  // Load the components
698
752
  var users_widgets = {}
699
753
  function make_component(name, func) {
700
754
  // Define the component
701
755
 
702
- window[name] = users_widgets[name] = window.React_View({
756
+ window[name] = users_widgets[name] = statebus.create_react_class({
703
757
  displayName: name,
704
758
  render: function () {
705
759
  var args = []