braid-http 1.1.1 → 1.3.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.
- package/braid-http-client.js +52 -10
- package/braid-http-server.js +74 -47
- package/package.json +1 -1
package/braid-http-client.js
CHANGED
|
@@ -189,18 +189,31 @@ async function braid_fetch (url, params = {}) {
|
|
|
189
189
|
if (params.patches.length === 1) {
|
|
190
190
|
let patch = params.patches[0]
|
|
191
191
|
params.headers.set('Content-Range', `${patch.unit} ${patch.range}`)
|
|
192
|
-
|
|
192
|
+
|
|
193
|
+
if (typeof patch.content === 'string')
|
|
194
|
+
patch.content = new TextEncoder().encode(patch.content)
|
|
195
|
+
|
|
193
196
|
params.body = patch.content
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
// Multiple patches get sent within a Patches: N block
|
|
197
200
|
else {
|
|
198
201
|
params.headers.set('Patches', params.patches.length)
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
let bufs = []
|
|
203
|
+
let te = new TextEncoder()
|
|
204
|
+
for (let patch of params.patches) {
|
|
205
|
+
if (bufs.length) bufs.push(te.encode(`\r\n`))
|
|
206
|
+
|
|
207
|
+
if (typeof patch.content === 'string')
|
|
208
|
+
patch.content = te.encode(patch.content)
|
|
209
|
+
|
|
210
|
+
var length = `content-length: ${get_binary_length(patch.content)}`
|
|
201
211
|
var range = `content-range: ${patch.unit} ${patch.range}`
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
bufs.push(te.encode(`${length}\r\n${range}\r\n\r\n`))
|
|
213
|
+
bufs.push(patch.content)
|
|
214
|
+
bufs.push(te.encode(`\r\n`))
|
|
215
|
+
}
|
|
216
|
+
params.body = new Blob(bufs)
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
|
|
@@ -306,10 +319,13 @@ async function braid_fetch (url, params = {}) {
|
|
|
306
319
|
// Each time something happens, we'll either get a new
|
|
307
320
|
// version back, or an error.
|
|
308
321
|
(result, err) => {
|
|
309
|
-
if (!err)
|
|
322
|
+
if (!err) {
|
|
323
|
+
// check whether we aborted
|
|
324
|
+
if (original_signal?.aborted) throw new DOMException('already aborted', 'AbortError')
|
|
325
|
+
|
|
310
326
|
// Yay! We got a new version! Tell the callback!
|
|
311
327
|
cb(result)
|
|
312
|
-
else
|
|
328
|
+
} else
|
|
313
329
|
// This error handling code runs if the connection
|
|
314
330
|
// closes, or if there is unparseable stuff in the
|
|
315
331
|
// streamed response.
|
|
@@ -486,7 +502,18 @@ var subscription_parser = (cb) => ({
|
|
|
486
502
|
}
|
|
487
503
|
})
|
|
488
504
|
|
|
489
|
-
|
|
505
|
+
for (let p of update.patches ?? []) {
|
|
506
|
+
Object.defineProperty(p, 'content_text', {
|
|
507
|
+
get: () => new TextDecoder('utf-8').decode(p.content)
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
this.cb(update)
|
|
513
|
+
} catch (e) {
|
|
514
|
+
this.cb(null, e)
|
|
515
|
+
return
|
|
516
|
+
}
|
|
490
517
|
|
|
491
518
|
// Reset the parser for the next version!
|
|
492
519
|
this.state = {input: this.state.input}
|
|
@@ -551,6 +578,15 @@ function parse_headers (input) {
|
|
|
551
578
|
var h = extractHeader(input)
|
|
552
579
|
if (!h) return {result: 'waiting'}
|
|
553
580
|
|
|
581
|
+
// Skip "HTTP 104 Multiresponse"
|
|
582
|
+
if (h.header_string.startsWith('HTTP 104')) {
|
|
583
|
+
h = extractHeader(h.remaining_bytes)
|
|
584
|
+
if (!h) return {result: 'waiting'}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Skip "HTTP 200 OK"
|
|
588
|
+
h.header_string = h.header_string.replace(/^HTTP 200.*\r?\n/, '')
|
|
589
|
+
|
|
554
590
|
var headers_source = h.header_string
|
|
555
591
|
var headers_length = headers_source.length
|
|
556
592
|
|
|
@@ -629,7 +665,7 @@ function parse_body (state) {
|
|
|
629
665
|
state.patches = [{
|
|
630
666
|
unit: match.unit,
|
|
631
667
|
range: match.range,
|
|
632
|
-
content:
|
|
668
|
+
content: new Uint8Array(state.input.slice(0, content_length)),
|
|
633
669
|
|
|
634
670
|
// Question: Perhaps we should include headers here, like we do for
|
|
635
671
|
// the Patches: N headers below?
|
|
@@ -715,7 +751,7 @@ function parse_body (state) {
|
|
|
715
751
|
|
|
716
752
|
last_patch.unit = match.unit
|
|
717
753
|
last_patch.range = match.range
|
|
718
|
-
last_patch.content =
|
|
754
|
+
last_patch.content = new Uint8Array(state.input.slice(0, content_length))
|
|
719
755
|
last_patch.extra_headers = extra_headers(last_patch.headers)
|
|
720
756
|
delete last_patch.headers // We only keep the extra headers ^^
|
|
721
757
|
|
|
@@ -811,6 +847,12 @@ function extractHeader(input) {
|
|
|
811
847
|
};
|
|
812
848
|
}
|
|
813
849
|
|
|
850
|
+
function get_binary_length(x) {
|
|
851
|
+
return x instanceof ArrayBuffer ? x.byteLength :
|
|
852
|
+
x instanceof Uint8Array ? x.length :
|
|
853
|
+
x instanceof Blob ? x.size : undefined
|
|
854
|
+
}
|
|
855
|
+
|
|
814
856
|
// ****************************
|
|
815
857
|
// Exports
|
|
816
858
|
// ****************************
|
package/braid-http-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var assert = require('assert')
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Writes patches in pseudoheader format.
|
|
4
4
|
//
|
|
5
5
|
// The `patches` argument can be:
|
|
6
6
|
// - Array of patches
|
|
@@ -25,9 +25,7 @@ var assert = require('assert')
|
|
|
25
25
|
//
|
|
26
26
|
// {"some": "json object"}
|
|
27
27
|
//
|
|
28
|
-
function
|
|
29
|
-
var result = ''
|
|
30
|
-
|
|
28
|
+
function write_patches(res, patches) {
|
|
31
29
|
// `patches` must be a patch object or an array of patch objects
|
|
32
30
|
// - Object: {unit, range, content}
|
|
33
31
|
// - Array: [{unit, range, content}, ...]
|
|
@@ -38,7 +36,7 @@ function generate_patches(res, patches) {
|
|
|
38
36
|
if (Array.isArray(patches)) {
|
|
39
37
|
|
|
40
38
|
// Add `Patches: N` header if array
|
|
41
|
-
|
|
39
|
+
res.write(`Patches: ${patches.length}\r\n\r\n`)
|
|
42
40
|
} else
|
|
43
41
|
// Else, we'll out put a single patch
|
|
44
42
|
patches = [patches]
|
|
@@ -47,29 +45,35 @@ function generate_patches(res, patches) {
|
|
|
47
45
|
patches.forEach((patch, i) => {
|
|
48
46
|
assert(typeof patch.unit === 'string')
|
|
49
47
|
assert(typeof patch.range === 'string')
|
|
50
|
-
|
|
48
|
+
|
|
49
|
+
if (typeof patch.content === 'string')
|
|
50
|
+
patch.content = new TextEncoder().encode(patch.content)
|
|
51
51
|
|
|
52
52
|
if (i > 0)
|
|
53
|
-
|
|
53
|
+
res.write('\r\n\r\n')
|
|
54
54
|
|
|
55
55
|
let extra_headers = Object.fromEntries(Object.entries(patch).filter(([k, v]) => k != 'unit' && k != 'range' && k != 'content'))
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
res.write(`Content-Length: ${get_binary_length(patch.content)}\r
|
|
58
58
|
Content-Range: ${patch.unit} ${patch.range}\r
|
|
59
59
|
${Object.entries(extra_headers).map(([k, v]) => `${k}: ${v}\r\n`).join('')}\r
|
|
60
|
-
|
|
60
|
+
`)
|
|
61
|
+
write_binary(res, patch.content)
|
|
61
62
|
})
|
|
62
|
-
return result
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
// Deprecated method for legacy support
|
|
67
67
|
function parse_patches (req, cb) {
|
|
68
68
|
parse_update(req, update => {
|
|
69
|
-
if (
|
|
69
|
+
if (update.body != null) {
|
|
70
70
|
// Return body as an "everything" patch
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
let patch = {unit: 'everything', range: '', content: update.body}
|
|
72
|
+
Object.defineProperty(patch, 'content_text', {
|
|
73
|
+
get: () => new TextDecoder('utf-8').decode(patch.content)
|
|
74
|
+
})
|
|
75
|
+
cb([patch])
|
|
76
|
+
} else
|
|
73
77
|
cb(update.patches)
|
|
74
78
|
})
|
|
75
79
|
}
|
|
@@ -80,16 +84,20 @@ function parse_update (req, cb) {
|
|
|
80
84
|
var num_patches = req.headers.patches
|
|
81
85
|
|
|
82
86
|
if (!num_patches && !req.headers['content-range']) {
|
|
83
|
-
var
|
|
84
|
-
req.on('data', chunk =>
|
|
87
|
+
var buffer = []
|
|
88
|
+
req.on('data', chunk => buffer.push(chunk))
|
|
85
89
|
req.on('end', () => {
|
|
86
|
-
|
|
90
|
+
let body = new Uint8Array(Buffer.concat(buffer))
|
|
91
|
+
let update = { body, patches: undefined }
|
|
92
|
+
Object.defineProperty(update, 'body_text', {
|
|
93
|
+
get: () => new TextDecoder('utf-8').decode(update.body)
|
|
94
|
+
})
|
|
95
|
+
cb(update)
|
|
87
96
|
})
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
// Parse a single patch, lacking Patches: N
|
|
91
100
|
else if (num_patches === undefined && req.headers['content-range']) {
|
|
92
|
-
|
|
93
101
|
// We only support range patches right now, so there must be a
|
|
94
102
|
// Content-Range header.
|
|
95
103
|
assert(req.headers['content-range'], 'No patches to parse: need `Patches: N` or `Content-Range:` header in ' + JSON.stringify(req.headers))
|
|
@@ -104,8 +112,11 @@ function parse_update (req, cb) {
|
|
|
104
112
|
req.on('data', chunk => buffer.push(chunk))
|
|
105
113
|
// Then return it
|
|
106
114
|
req.on('end', () => {
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
let patch = {unit, range, content: new Uint8Array(Buffer.concat(buffer))}
|
|
116
|
+
Object.defineProperty(patch, 'content_text', {
|
|
117
|
+
get: () => new TextDecoder('utf-8').decode(patch.content)
|
|
118
|
+
})
|
|
119
|
+
cb({ patches: [patch], body: undefined })
|
|
109
120
|
})
|
|
110
121
|
}
|
|
111
122
|
|
|
@@ -136,7 +147,7 @@ function parse_update (req, cb) {
|
|
|
136
147
|
if (!('content-length' in headers)) {
|
|
137
148
|
// Print a nice error if it's missing
|
|
138
149
|
console.error('No content-length in', JSON.stringify(headers),
|
|
139
|
-
'from', {buffer})
|
|
150
|
+
'from', new TextDecoder().decode(new Uint8Array(buffer)), {buffer})
|
|
140
151
|
process.exit(1)
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -150,10 +161,14 @@ function parse_update (req, cb) {
|
|
|
150
161
|
|
|
151
162
|
// Content-range is of the form '<unit> <range>' e.g. 'json .index'
|
|
152
163
|
var [unit, range] = parse_content_range(headers['content-range'])
|
|
153
|
-
var patch_content = new
|
|
164
|
+
var patch_content = new Uint8Array(h.remaining_bytes.slice(0, body_length))
|
|
154
165
|
|
|
155
166
|
// We've got our patch!
|
|
156
|
-
|
|
167
|
+
let patch = {unit, range, content: patch_content}
|
|
168
|
+
Object.defineProperty(patch, 'content_text', {
|
|
169
|
+
get: () => new TextDecoder('utf-8').decode(patch.content)
|
|
170
|
+
})
|
|
171
|
+
patches.push(patch)
|
|
157
172
|
|
|
158
173
|
buffer = h.remaining_bytes.slice(body_length)
|
|
159
174
|
}
|
|
@@ -215,7 +230,7 @@ function braidify (req, res, next) {
|
|
|
215
230
|
(done, err) => parse_patches(
|
|
216
231
|
req,
|
|
217
232
|
(patches) => done(patches.map(
|
|
218
|
-
p => ({...p, content: JSON.parse(p.
|
|
233
|
+
p => ({...p, content: JSON.parse(p.content_text)})
|
|
219
234
|
))
|
|
220
235
|
)
|
|
221
236
|
)
|
|
@@ -297,7 +312,7 @@ function braidify (req, res, next) {
|
|
|
297
312
|
next && next()
|
|
298
313
|
}
|
|
299
314
|
|
|
300
|
-
function send_update(res, data, url, peer) {
|
|
315
|
+
async function send_update(res, data, url, peer) {
|
|
301
316
|
var {version, parents, patches, patch, body} = data
|
|
302
317
|
|
|
303
318
|
function set_header (key, val) {
|
|
@@ -308,7 +323,7 @@ function send_update(res, data, url, peer) {
|
|
|
308
323
|
}
|
|
309
324
|
function write_body (body) {
|
|
310
325
|
if (res.isSubscription) res.write('\r\n')
|
|
311
|
-
res
|
|
326
|
+
write_binary(res, body)
|
|
312
327
|
}
|
|
313
328
|
|
|
314
329
|
// console.log('sending version', {url, peer, version, parents, patches, body,
|
|
@@ -316,14 +331,10 @@ function send_update(res, data, url, peer) {
|
|
|
316
331
|
|
|
317
332
|
// Validate that the body and patches are strings,
|
|
318
333
|
// or in the case of body, it could be binary
|
|
319
|
-
if (body !== undefined)
|
|
320
|
-
assert(typeof body === 'string' ||
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
body instanceof Blob ||
|
|
324
|
-
body instanceof Buffer
|
|
325
|
-
)
|
|
326
|
-
else {
|
|
334
|
+
if (body !== undefined) {
|
|
335
|
+
assert(typeof body === 'string' || get_binary_length(body) != null)
|
|
336
|
+
if (body instanceof Blob) body = await body.arrayBuffer()
|
|
337
|
+
} else {
|
|
327
338
|
// Only one of patch or patches can be set
|
|
328
339
|
assert(!(patch && patches))
|
|
329
340
|
assert((patch || patches) !== undefined)
|
|
@@ -339,23 +350,31 @@ function send_update(res, data, url, peer) {
|
|
|
339
350
|
|
|
340
351
|
// Now `patches` will be an array of patches or a single patch object.
|
|
341
352
|
//
|
|
342
|
-
// This distinction is used in
|
|
353
|
+
// This distinction is used in write_patches() to determine whether
|
|
343
354
|
// to inline a single patch in the update body vs. writing out a
|
|
344
355
|
// Patches: N block.
|
|
345
356
|
assert(typeof patches === 'object')
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
for (let p of Array.isArray(patches) ? patches : [patch]) {
|
|
358
|
+
assert('unit' in p)
|
|
359
|
+
assert('range' in p)
|
|
360
|
+
assert('content' in p)
|
|
361
|
+
assert(typeof p.content === 'string' || get_binary_length(p.content) != null)
|
|
362
|
+
if (p.content instanceof Blob) p.content = await p.content.arrayBuffer()
|
|
363
|
+
}
|
|
353
364
|
}
|
|
354
365
|
|
|
355
366
|
var body_exists = body || body === ''
|
|
356
367
|
assert(body_exists || patches, 'Missing body or patches')
|
|
357
368
|
assert(!(body_exists && patches), 'Cannot send both body and patches')
|
|
358
369
|
|
|
370
|
+
// Write the beginning of a new multiresponse response
|
|
371
|
+
if (!res.wrote_104_multiresponse) {
|
|
372
|
+
res.write(`HTTP 104 Multiresponse\r\n\r\n`)
|
|
373
|
+
res.wrote_104_multiresponse = true
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
res.write(`HTTP 200 OK\r\n`)
|
|
377
|
+
|
|
359
378
|
// Write the headers or virtual headers
|
|
360
379
|
for (var [header, value] of Object.entries(data)) {
|
|
361
380
|
header = header.toLowerCase()
|
|
@@ -384,14 +403,10 @@ function send_update(res, data, url, peer) {
|
|
|
384
403
|
// Write the patches or body
|
|
385
404
|
if (body_exists) {
|
|
386
405
|
let x = typeof body === 'string' ? new TextEncoder().encode(body) : body
|
|
387
|
-
set_header('Content-Length',
|
|
388
|
-
x instanceof ArrayBuffer ? x.byteLength :
|
|
389
|
-
x instanceof Uint8Array ? x.length :
|
|
390
|
-
x instanceof Blob ? x.size :
|
|
391
|
-
x instanceof Buffer ? x.length : null)
|
|
406
|
+
set_header('Content-Length', get_binary_length(x))
|
|
392
407
|
write_body(x)
|
|
393
408
|
} else
|
|
394
|
-
|
|
409
|
+
write_patches(res, patches)
|
|
395
410
|
|
|
396
411
|
// Add a newline to prepare for the next version
|
|
397
412
|
// See also https://github.com/braid-org/braid-spec/issues/73
|
|
@@ -461,4 +476,16 @@ function extractHeader(input) {
|
|
|
461
476
|
};
|
|
462
477
|
}
|
|
463
478
|
|
|
479
|
+
function get_binary_length(x) {
|
|
480
|
+
return x instanceof ArrayBuffer ? x.byteLength :
|
|
481
|
+
x instanceof Uint8Array ? x.length :
|
|
482
|
+
x instanceof Blob ? x.size :
|
|
483
|
+
x instanceof Buffer ? x.length : undefined
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function write_binary(res, body) {
|
|
487
|
+
if (body instanceof ArrayBuffer) body = new Uint8Array(body)
|
|
488
|
+
res.write(body)
|
|
489
|
+
}
|
|
490
|
+
|
|
464
491
|
module.exports = braidify
|