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 +70 -48
- package/braid-http-client.js +32 -8
- package/braid-http-server.js +33 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,7 +82,39 @@ fetch('https://braid.org/chat', {subscribe: true}).then(
|
|
|
82
82
|
)
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
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
|
-
|
|
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', {
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
package/braid-http-client.js
CHANGED
|
@@ -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
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
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....
|
package/braid-http-server.js
CHANGED
|
@@ -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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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")
|