braid-http 1.3.105 → 1.3.107

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.
package/README.md CHANGED
@@ -82,7 +82,39 @@ fetch('https://braid.org/chat', {subscribe: true}).then(
82
82
  )
83
83
  ```
84
84
 
85
- If you want automatic reconnections, this library add a `{retry: true}` option to `fetch()`.
85
+ ### Example Subscription with Async/Await
86
+
87
+ ```javascript
88
+ (await fetch('/chat', {subscribe: true, retry: true})).subscribe(
89
+ (update) => {
90
+ // We got a new update!
91
+ })
92
+ ```
93
+
94
+ ### Example Subscription with `for await`
95
+
96
+ ```javascript
97
+ var subscription_iterator = (await fetch('/chat',
98
+ {subscribe: true, retry: true})).subscription
99
+ for await (var update of subscription_iterator) {
100
+ // Updates might come in the form of patches:
101
+ if (update.patches)
102
+ chat = apply_patches(update.patches, chat)
103
+
104
+ // Or complete snapshots:
105
+ else
106
+ // Beware the server doesn't send these yet.
107
+ chat = JSON.parse(update.body_text)
108
+
109
+ render_stuff()
110
+ }
111
+ ```
112
+
113
+ ### Advanced client features
114
+
115
+ #### Automatic reconection
116
+
117
+ Pass a `{retry: true}` option to `fetch()` to automatically reconnect:
86
118
 
87
119
  ```javascript
88
120
  fetch('https://braid.org/chat', {subscribe: true, retry: true}).then(
@@ -95,12 +127,16 @@ fetch('https://braid.org/chat', {subscribe: true, retry: true}).then(
95
127
  )
96
128
  ```
97
129
 
98
- For use in conjunction with `{retry: true}`, it's possible to make the `parents` param equal to a function, which will be called to get the current parents each time the fetch establishes a new connection.
130
+
131
+ To update the parent version that you reconnect from, set the `parents`
132
+ paramter to a function rather than an array of strings:
99
133
 
100
134
  ```javascript
101
- fetch('https://braid.org/chat', {subscribe: true, retry: true, parents: () => {
102
- return current_parents
103
- }}).then(
135
+ fetch('https://braid.org/chat', {
136
+ subscribe: true,
137
+ retry: true,
138
+ parents: () => current_parents
139
+ }).then(
104
140
  res => res.subscribe(
105
141
  (update) => {
106
142
  console.log('We got a new update!', update)
@@ -110,7 +146,13 @@ fetch('https://braid.org/chat', {subscribe: true, retry: true, parents: () => {
110
146
  )
111
147
  ```
112
148
 
113
- You can monitor the subscription's connection status with `onSubscriptionStatus`:
149
+ It will call the `parents` function each time it reconnects to learn the
150
+ current parents to connection from.
151
+
152
+ #### Monitor the connection status
153
+
154
+ The `onSubscriptionStatus(status)` callback informs you when the connection
155
+ goes online and offline:
114
156
 
115
157
  ```javascript
116
158
  fetch('https://braid.org/chat', {
@@ -133,39 +175,13 @@ The callback receives an object with only the fields relevant to the event:
133
175
  - `{online: true}` — the subscription is connected
134
176
  - `{online: false, error}` — the subscription went offline, with the error/reason for disconnection
135
177
 
136
- ### Example Subscription with Async/Await
137
-
138
- ```javascript
139
- (await fetch('/chat', {subscribe: true, retry: true})).subscribe(
140
- (update) => {
141
- // We got a new update!
142
- })
143
- ```
144
-
145
- ### Example Subscription with `for await`
146
178
 
147
- ```javascript
148
- var subscription_iterator = (await fetch('/chat',
149
- {subscribe: true, retry: true})).subscription
150
- for await (var update of subscription_iterator) {
151
- // Updates might come in the form of patches:
152
- if (update.patches)
153
- chat = apply_patches(update.patches, chat)
154
-
155
- // Or complete snapshots:
156
- else
157
- // Beware the server doesn't send these yet.
158
- chat = JSON.parse(update.body_text)
159
-
160
- render_stuff()
161
- }
162
- ```
163
179
 
164
180
  ## Using it in Nodejs
165
181
 
166
182
  You can braidify your nodejs server with:
167
183
 
168
- ```
184
+ ```javascript
169
185
  var braidify = require('braid-http').http_server
170
186
  ```
171
187
 
@@ -344,19 +360,19 @@ fetch('https://localhost:3009/chat',
344
360
 
345
361
  Run all tests from the command line:
346
362
 
347
- ```
363
+ ```bash
348
364
  npm test
349
365
  ```
350
366
 
351
367
  Run tests in a browser (auto-opens):
352
368
 
353
- ```
369
+ ```bash
354
370
  npm run test:browser
355
371
  ```
356
372
 
357
373
  You can also filter tests by name:
358
374
 
359
- ```
375
+ ```bash
360
376
  node test/test.js --filter="version"
361
377
  ```
362
378
 
@@ -369,7 +385,7 @@ the scenes to overcome web browsers' 6-connection limit (with HTTP/1) and
369
385
  the recommended ways, you don't need to know it's happening — the abstraction
370
386
  is completely transparent.
371
387
 
372
- ```
388
+ ```javascript
373
389
  // Recommendation #1: Wrapping the entire request handler
374
390
  require('http').createServer(
375
391
  braidify((req, res) => {
@@ -378,18 +394,22 @@ require('http').createServer(
378
394
  )
379
395
  ```
380
396
 
381
- ```
397
+ ```javascript
382
398
  // Recommendation #2: As middleware
383
399
  var app = require('express')()
384
400
  app.use(braidify)
385
401
  ```
386
402
 
387
- ```
403
+ ```javascript
388
404
  // Recommendation #3: With braidify(req, res, next)
389
405
  // (Equivalent to the middleware form.)
390
- ...
391
- braidify(req, res, next)
392
- ...
406
+ app.use(
407
+ (req, res, next) => {
408
+ ...
409
+ braidify(req, res, next)
410
+ ...
411
+ }
412
+ )
393
413
  ```
394
414
 
395
415
 
@@ -397,14 +417,16 @@ app.use(braidify)
397
417
 
398
418
  If you are using braidify from within a library, or in another context without
399
419
  access to the entire request handler, or a `next()` method, then you can use
400
- the inline form:
420
+ the inline `braidify(req, res)` form:
401
421
 
402
- ```
422
+ ```javascript
423
+ require('http').createServer(
403
424
  (req, res) => {
404
- ...
405
- braidify(req, res); if (req.is_multiplexer) return
406
- ...
407
- })
425
+ ...
426
+ braidify(req, res); if (req.is_multiplexer) return
427
+ ...
428
+ }
429
+ )
408
430
  ```
409
431
 
410
432
  Just know that there are three abstraction leaks when using this form:
@@ -159,20 +159,21 @@ async function braid_fetch (url, params = {}) {
159
159
  params.headers.set('version', params.version.map(JSON.stringify).map(ascii_ify).join(', '))
160
160
  if (Array.isArray(params.parents))
161
161
  params.headers.set('parents', params.parents.map(JSON.stringify).map(ascii_ify).join(', '))
162
- if (params.subscribe)
162
+ if (params.subscribe) {
163
163
  params.headers.set('subscribe', 'true')
164
+ // Prevent this response from being cached
165
+ params.cache = 'no-store'
166
+ }
167
+
164
168
  if (params.peer)
165
169
  params.headers.set('peer', params.peer)
166
-
170
+
167
171
  if (params.heartbeats)
168
172
  params.headers.set('heartbeats',
169
173
  typeof params.heartbeats === 'number'
170
174
  ? `${params.heartbeats}s`
171
175
  : params.heartbeats)
172
176
 
173
- // Prevent browsers from going to disk cache
174
- params.cache = 'no-cache'
175
-
176
177
  // Prepare patches
177
178
  if (params.patches) {
178
179
  console.assert(!params.body, 'Cannot send both patches and body')
@@ -311,9 +312,32 @@ async function braid_fetch (url, params = {}) {
311
312
  if (parents) params.headers.set('parents', parents.map(JSON.stringify).join(', '))
312
313
  }
313
314
 
314
- // undocumented feature used by braid-chrome
315
- // to see the fetch args as they are right before it is actually called,
316
- // to display them for the user in the dev panel
315
+ // Work around Chrome bug where when you restore a closed tab,
316
+ // Chrome overrides our {cache:'no-store'} parameter and instead sets
317
+ // SKIP_CACHE_VALIDATION, which overrides our subscription response
318
+ // with ... a static entry from its cache! That sucks.
319
+ //
320
+ // See https://issues.chromium.org/issues/490673934
321
+ //
322
+ // Our workaround is to detect if we are in chrome, and subscribing,
323
+ // and are within a page restoration... and then...
324
+ if (!is_nodejs && params.headers.has('subscribe')) {
325
+ var nav_entry = performance?.getEntriesByType?.('navigation')?.[0]
326
+ if (nav_entry?.type === 'back_forward'
327
+ && document.readyState !== 'complete')
328
+
329
+ // ...we just wait until the page has loaded to send this fetch
330
+ await new Promise(r => window.addEventListener('load', r))
331
+
332
+ // Because the SKIP_CACHE_VALIDATION policy in chrome goes away
333
+ // as soon as the page loads.
334
+ }
335
+
336
+ // Braid-Chrome needs the following special (undocumented)
337
+ // `onFetch` feature. It's a callback with the params as they
338
+ // are being sent to the underlying fetch(). The Braid
339
+ // devtools wants to be able to display these in its devtools
340
+ // panel.
317
341
  params.onFetch?.(url, params, underlying_aborter)
318
342
 
319
343
  // Now we run the original fetch....
@@ -332,12 +332,22 @@ function braidify (req, res, next) {
332
332
  if ((subscribe === '' || subscribe)
333
333
  // And this is a GET, because `Subscribe:` is only
334
334
  // specified for GET thus far...
335
- && req.method === 'GET')
335
+ && req.method === 'GET') {
336
336
  // Then let's set 'subscribe' on. We default to "true", but if the
337
337
  // client actually specified a value other than empty string '', let's
338
338
  // use that rich value.
339
339
  subscribe = subscribe || true
340
340
 
341
+ // Great. Now we also need to set the response body's content-type, so
342
+ // that FireFox doesn't try to sniff the content-type on a stream and
343
+ // hang forever waiting for 512 bytes (see firefox issue
344
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1544313)
345
+ res.setHeader('Content-Type', 'message/http-sequence')
346
+
347
+ // And we don't want any caches trying to store these stream bodies.
348
+ res.setHeader('Cache-Control', 'no-store')
349
+ }
350
+
341
351
  // Define convenience variables
342
352
  req.version = version
343
353
  req.parents = parents
@@ -649,9 +659,19 @@ function braidify (req, res, next) {
649
659
  res.isSubscription = true
650
660
 
651
661
  // Let's disable the timeouts (if it exists)
652
- if (req.socket.server)
662
+ if (req.socket.server) {
653
663
  req.socket.server.timeout = 0.0
654
664
 
665
+ // Node 18+ added requestTimeout (default 300s) and
666
+ // headersTimeout (default 60s) which will kill idle
667
+ // long-lived connections — our bread and butter. We disable
668
+ // the requestTimeout, but the headersTimeout is probably
669
+ // fine.
670
+ //
671
+ req.socket.server.requestTimeout = 0
672
+ // req.socket.server.headersTimeout = 0
673
+ }
674
+
655
675
  // We have a subscription!
656
676
  res.statusCode = 209
657
677
  res.statusMessage = 'Multiresponse'
@@ -753,12 +773,12 @@ async function send_update(res, data, url, peer) {
753
773
  // Validate the body and patches
754
774
  assert(!(patch && patches),
755
775
  'sendUpdate: cannot have both `update.patch` and `update.patches` set')
756
- if (patch)
757
- patches = [patch]
758
776
  assert(!(body && patches),
759
777
  'sendUpdate: cannot have both `update.body` and `update.patch(es)')
760
778
  assert(!patches || Array.isArray(patches),
761
779
  'sendUpdate: `patches` provided is not array')
780
+ if (patch)
781
+ patches = patch
762
782
 
763
783
  // Validate body format
764
784
  if (body !== undefined) {
@@ -838,10 +858,15 @@ async function send_update(res, data, url, peer) {
838
858
  // See also https://github.com/braid-org/braid-spec/issues/73
839
859
  if (res.isSubscription) {
840
860
  var extra_newlines = 1
841
- if (res.is_firefox)
842
- // Work around Firefox network buffering bug
843
- // See https://github.com/braid-org/braidjs/issues/15
844
- extra_newlines = 240
861
+
862
+ // Note: this firefox workaround was replaced with a content-type fix
863
+ // above. We realized that content-type fixes the issue when we found
864
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1544313
865
+ //
866
+ // if (res.is_firefox)
867
+ // // Work around Firefox network buffering bug
868
+ // // See https://github.com/braid-org/braidjs/issues/15
869
+ // extra_newlines = 240
845
870
 
846
871
  for (var i = 0; i < 1 + extra_newlines; i++)
847
872
  res.write("\r\n")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-http",
3
- "version": "1.3.105",
3
+ "version": "1.3.107",
4
4
  "description": "An implementation of Braid-HTTP for Node.js and Browsers",
5
5
  "scripts": {
6
6
  "test": "node test/test.js",