| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
This ponyfill library extends the HTTP implementations of Browsers and Nodejs with Braid-HTTP; transforming them from state transfer to state synchronization systems.
These features are provided in an elegant, backwards-compatible way:
It provides the ability to parse and generate Braid-HTTP headers, patches, and multiresponse subscriptions, conforming to the Braid-HTTP v04 specification, with the additional HTTP Multiresponse and Multiplexing v1.0 extensions.
Developed in braid.org.
Browsers:
<script src="https://unpkg.com/braid-http/braid-http-client.js"></script>
<script>
// To live on the cutting edge, you can now replace the browser's fetch() if desired:
// window.fetch = braid_fetch
</script>Node.js:
npm install braid-http// Import with require()
require('braid-http').fetch // A polyfill for fetch
require('braid-http').braidify // Braidifies node servers, handlers, and (req, res)
// Or as es6 module
import {fetch, braidify} from 'braid-http'This library adds a {subscribe: true} option to fetch(), and lets you access the result of a subscription with these new fields on the fetch response:
Here is an example of subscribing to a Braid resource using promises:
fetch('https://braid.org/chat', {subscribe: true}).then(
res => res.subscribe(
(update) => {
console.log('We got a new update!', update)
// {
// version: ["me"],
// parents: ["mom", "dad"],
// patches: [{
//. unit: "json",
// range: ".foo",
// content: new Uint8Array([51]),
// content_text: "3" <-- getter
//. }],
// body: new Uint8Array([51]),
// body_text: "3" <-- getter
// }
//
// Note that `update` will contain either patches *or* body
}
)
)(await fetch('/chat', {subscribe: true, retry: true})).subscribe(
(update) => {
// We got a new update!
})var subscription_iterator = (await fetch('/chat',
{subscribe: true, retry: true})).subscription
for await (var update of subscription_iterator) {
// Updates might come in the form of patches:
if (update.patches)
chat = apply_patches(update.patches, chat)
// Or complete snapshots:
else
// Beware the server doesn't send these yet.
chat = JSON.parse(update.body_text)
render_stuff()
}Pass a {retry: true} option to fetch() to automatically reconnect:
fetch('https://braid.org/chat', {subscribe: true, retry: true}).then(
res => res.subscribe(
(update) => {
console.log('We got a new update!', update)
// Do something with the update
}
)
)To update the parent version that you reconnect from, set the parents paramter to a function rather than an array of strings:
fetch('https://braid.org/chat', {
subscribe: true,
retry: true,
parents: () => current_parents
}).then(
res => res.subscribe(
(update) => {
console.log('We got a new update!', update)
// Do something with the update
}
)
)It will call the parents function each time it reconnects to learn the current parents to connection from.
The onSubscriptionStatus(status) callback informs you when the connection goes online and offline:
fetch('https://braid.org/chat', {
subscribe: true,
retry: true,
onSubscriptionStatus: ({online, error, status, statusText}) => {
if (online)
console.log('Connected!')
else
console.log('Disconnected:', error)
}
}).then(
res => res.subscribe(
(update) => { console.log('Got update!', update) }
)
)The callback receives an object with only the fields relevant to the event:
reliable_update_channel(url, options) is a higher-level API built on top of braid_fetch that gives you a reliably-synced subscription plus a PUT queue that survives network failures. It implements the Reliable Updates spec: it reconnects automatically, detects dead connections via heartbeats, retries failed PUTs, honors Retry-After, warns on unexpected status codes, and aborts on unrecoverable errors.
var { reliable_update_channel } = require('braid-http')
var current_version = []
var channel = reliable_update_channel('https://braid.org/chat', {
reconnect_from_parents: () => current_version,
on_update: (update) => {
if (update.version) current_version = update.version
// Apply the update to your local state
},
on_status: ({online, outstanding_puts}) => {},
on_warning: (msg) => console.warn('reliable_update_channel:', msg),
on_error: (err) => console.error('reliable_update_channel shut down:', err),
get_headers: {},
put_headers: {},
timeout: 30
})
// PUTs are queued and retried automatically. The returned promise
// resolves when the server has acknowledged this specific PUT.
await channel.put({
version: ['me-1'],
patches: [{unit: 'text', range: '[0:0]', content: 'hello'}]
})
// Shut down the controller
channel.close()| Option | Default | Description |
|---|---|---|
| signal | — | AbortSignal. When it aborts, the subscription stops, the PUT queue is drained with rejections, and no further retries happen. |
| on_update | — | (update) => .... Called for each update received on the subscription. Same shape as braid_fetch's subscribe callback. |
| on_warning | console.warn | (msg) => .... Called for unexpected-but-recoverable conditions (e.g. a 500 on a PUT retry, or a parse error that triggers shutdown). |
| on_error | — | (err) => .... Called once when reliable_update_channel shuts itself down due to a fatal condition (e.g. a subscription parse error). Not called when the caller aborts signal. |
| parents | — | Array or callback returning the latest versions the client knows about. Called fresh on every reconnect so the server can resume from the right point. |
| headers | — | Extra HTTP headers to include on every GET and PUT (e.g. Cookie, Authorization, Accept). Per-PUT headers passed through put() override these on conflicts. |
| heartbeats | 20 | Heartbeat period in seconds. Sent as the Heartbeats request header; if the server echoes it back and the client doesn't see any bytes for 1.2 × heartbeats + 3 seconds, it reconnects. |
| put_timeout | heartbeats | Per-PUT timeout in seconds. If a PUT doesn't complete in time, all in-flight PUTs are aborted and the queue is retried. |
reliable_update_channel() returns { put }:
Once you've wired up reliable_update_channel, you get the reliable-updates behaviors for free:
You can braidify your nodejs server with:
var {braidify} = require('braid-http')Braidify adds these new abilities to requests and responses:
You can call it in two ways:
var {braidify} = require('braid-http')
// or:
import {braidify} from 'braid-http'
require('http').createServer(
braidify((req, res) => {
// Now braid stuff is available on req and res
// So you can easily handle subscriptions
if (req.subscribe)
res.startSubscription({ onClose: _=> null })
// startSubscription automatically sets statusCode = 209
else
res.statusCode = 200
// And send updates over a subscription
res.sendUpdate({
version: ['greg'],
body: JSON.stringify({greg: 'greg'})
})
})
).listen(9935)If you are working from a library, or from code that does not have access to the root of the HTTP handler or next in (req, res, next), you can also call braidify inline:
require('http').createServer(
(req, res) => {
braidify(req, res); if (req.is_multiplexer) return
// Now braid stuff is available on req and res
// ...
})
).listen(9935)This works, but the inline form leaks the multiplexing abstraction in three minor ways.
Or if you're using express, you can just call app.use(braidify) to get braid features added to every request and response.
var {braidify} = require('braid-http')
// or:
import {braidify} from 'braid-http'
var app = require('express')()
app.use(braidify) // Add braid stuff to req and res
app.get('/', (req, res) => {
// Now use it
if (req.subscribe)
res.startSubscription({ onClose: _=> null })
// startSubscription automatically sets statusCode = 209
else
res.statusCode = 200
// Send the current version
res.sendUpdate({
version: ['greg'],
parents: ['gr','eg'],
body: JSON.stringify({greg: 'greg'})
})
// Or you can send patches like this:
// res.sendUpdate({
// version: ['greg'],
// parents: ['gr','eg'],
// patches: [{range: '.greg', unit: 'json', content: '"greg"'}]
// })
})
require('http').createServer(app).listen(8583)var fetch = require('braid-http').fetch
// or:
import {fetch} from 'braid-http'
// process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0
fetch('https://localhost:3009/chat',
{subscribe: true}).andThen(
x => console.log('Got ', x)
)Run all tests from the command line:
npm testRun tests in a browser (auto-opens):
npm run test:browserYou can also filter tests by name:
node test/test.js --filter="version"This library automatically multiplexes subscriptions behind the scenes to overcome web browsers' 6-connection limit (with HTTP/1) and 100-connection limit (with HTTP/2).
Multiplexing is leakproof with the following forms of braidify:
// Recommendation #1: Wrapping the entire request handler
require('http').createServer(
braidify((req, res) => {
...
})
)// Recommendation #2: As middleware
var app = require('express')()
app.use(braidify)// Recommendation #3: With braidify(req, res, next)
// (Equivalent to the middleware form.)
app.use(
(req, res, next) => {
...
braidify(req, res, next)
...
}
)If you are using braidify from within a library, or in another context without access to the entire request handler, or a next() method, then you can use the inline braidify(req, res) form:
require('http').createServer(
(req, res) => {
...
braidify(req, res); if (req.is_multiplexer) return
...
}
)Just know that there are three abstraction leaks when using this form:
The buffering works like this: when the client connects to a new host, it sends a POST to create the multiplexer and GETs to subscribe — all in parallel. Sometimes a GET arrives before the POST. With the recommended forms, the server briefly buffers the GET (70ms, event-driven) until the POST lands, then processes it normally. Without next, the server can't re-run the handler, so it returns 424 immediately and the client retries.
You can tune multiplexing on the client, per-request or globally:
braid_fetch('/a', {multiplex: true}) // force on for this request
braid_fetch('/a', {multiplex: false}) // force off for this request
braid_fetch.enable_multiplex = true // on for all GETs
braid_fetch.enable_multiplex = false // off globally
braid_fetch.enable_multiplex = {after: 1} // on after N connections (default)And on the server:
braidify.enable_multiplex = true // default; set false to disable
braidify.multiplex_wait = 10 // ms; timeout for the buffering optimization (default 10)
// set to 0 to disable buffering| Back | FazBrowse Home | New Git URL |