braid-http 1.3.15 → 1.3.17

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.
@@ -64,16 +64,19 @@ function braidify_http (http) {
64
64
  on_update = f
65
65
 
66
66
  // And set up a subscription parser
67
- var parser = subscription_parser((update, error) => {
67
+ var parser = subscription_parser(async (update, error) => {
68
68
  if (!error)
69
- on_update && on_update(update)
69
+ on_update && (await on_update(update))
70
70
  else
71
71
  on_error && on_error(error)
72
72
  })
73
73
 
74
74
  // That will run each time we get new data
75
+ var chain = Promise.resolve()
75
76
  res.orig_on('data', (chunk) => {
76
- parser.read(chunk)
77
+ chain = chain.then(async () => {
78
+ await parser.read(chunk)
79
+ })
77
80
  })
78
81
  }
79
82
 
@@ -228,30 +231,61 @@ async function braid_fetch (url, params = {}) {
228
231
  var subscription_error = null
229
232
  var cb_running = false
230
233
 
234
+ // Multiplexing book-keeping;
235
+ // basically, if the user tries to make two or more subscriptions to the same origin,
236
+ // then we want to multiplex
237
+ var subscription_counts_on_close = null
238
+ if (params.headers.has('subscribe')) {
239
+ var origin = url[0] === '/' ? location.origin : new URL(url).origin
240
+ if (!braid_fetch.subscription_counts)
241
+ braid_fetch.subscription_counts = {}
242
+ braid_fetch.subscription_counts[origin] =
243
+ (braid_fetch.subscription_counts[origin] ?? 0) + 1
244
+
245
+ subscription_counts_on_close = () => {
246
+ subscription_counts_on_close = null
247
+ braid_fetch.subscription_counts[origin]--
248
+ if (!braid_fetch.subscription_counts[origin])
249
+ delete braid_fetch.subscription_counts[origin]
250
+ }
251
+ }
252
+
231
253
  return await new Promise((done, fail) => {
232
254
  connect()
233
255
  async function connect() {
256
+ // we direct all error paths here so we can make centralized retry decisions
234
257
  let on_error = e => {
235
258
  on_error = () => {}
236
259
 
237
- if (!params.retry ||
238
- e.name === "AbortError" ||
239
- e.startsWith?.('Parse error in headers') ||
240
- cb_running) {
241
-
260
+ // The fetch is probably down already, but there are some other errors that could have happened,
261
+ // and in those cases, we want to make sure to close the fetch
262
+ underlying_aborter?.abort()
263
+
264
+ // see if we should retry..
265
+ var retry = params.retry && // only try to reconnect if the user has chosen to
266
+ e.name !== "AbortError" && // don't retry if the user has chosen to abort
267
+ !e.startsWith?.('Parse error in headers') && // in this case, the server is spewing garbage, so reconnecting might be bad
268
+ !cb_running // if an error is thrown in the callback, then it may not be good to reconnect, and generate more errors
269
+
270
+ if (retry && !original_signal?.aborted) {
271
+ // retry after some time..
272
+ console.log(`retrying in ${waitTime}s: ${url} after error: ${e}`)
273
+ setTimeout(connect, waitTime * 1000)
274
+ waitTime = Math.min(waitTime + 1, 3)
275
+ } else {
276
+ // if we would have retried except that original_signal?.aborted,
277
+ // then we want to return that as the error..
278
+ if (retry && original_signal?.aborted) e = create_abort_error('already aborted')
279
+
280
+ // let people know things are shutting down..
281
+ subscription_counts_on_close?.()
242
282
  subscription_error?.(e)
243
283
  return fail(e)
244
284
  }
245
-
246
- underlying_aborter.abort()
247
-
248
- console.log(`retrying in ${waitTime}s: ${url} after error: ${e}`)
249
- setTimeout(connect, waitTime * 1000)
250
- waitTime = Math.min(waitTime + 1, 3)
251
285
  }
252
286
 
253
287
  try {
254
- if (original_signal?.aborted) throw new DOMException('already aborted', 'AbortError')
288
+ if (original_signal?.aborted) throw create_abort_error('already aborted')
255
289
 
256
290
  // We need a fresh underlying abort controller each time we connect
257
291
  underlying_aborter = new AbortController()
@@ -270,7 +304,34 @@ async function braid_fetch (url, params = {}) {
270
304
  params.onFetch?.(url, params)
271
305
 
272
306
  // Now we run the original fetch....
273
- res = await normal_fetch(url, params)
307
+
308
+ // try multiplexing if either of these is true:
309
+ // - they explicitly want multiplexing
310
+ // - this is not the first subscription to the same origin
311
+ if (braid_fetch.use_multiplexing &&
312
+ (params.headers.has('multiplexer') ||
313
+ (params.headers.has('subscribe') &&
314
+ braid_fetch.subscription_counts?.[origin] > 1))) {
315
+
316
+ // invent a new multiplexer and stream id
317
+ // if not provided
318
+ if (!params.headers.has('multiplexer')) {
319
+ // we want to keep the same multiplexer id for each origin
320
+ if (!braid_fetch.multiplexers)
321
+ braid_fetch.multiplexers = {}
322
+ if (!braid_fetch.multiplexers[origin])
323
+ braid_fetch.multiplexers[origin] =
324
+ Math.random().toString(36).slice(2)
325
+
326
+ // the stream id is different each time
327
+ var stream = Math.random().toString(36).slice(2)
328
+ params.headers.set('multiplexer',
329
+ `/${braid_fetch.multiplexers[origin]}/${stream}`)
330
+ }
331
+ res = await multiplex_fetch(url, params)
332
+ } else {
333
+ res = await normal_fetch(url, params)
334
+ }
274
335
 
275
336
  // And customize the response with a couple methods for getting
276
337
  // the braid subscription data:
@@ -312,14 +373,14 @@ async function braid_fetch (url, params = {}) {
312
373
 
313
374
  // Each time something happens, we'll either get a new
314
375
  // version back, or an error.
315
- (result, err) => {
376
+ async (result, err) => {
316
377
  if (!err) {
317
378
  // check whether we aborted
318
- if (original_signal?.aborted) throw new DOMException('already aborted', 'AbortError')
379
+ if (original_signal?.aborted) throw create_abort_error('already aborted')
319
380
 
320
381
  // Yay! We got a new version! Tell the callback!
321
382
  cb_running = true
322
- cb(result)
383
+ await cb(result)
323
384
  cb_running = false
324
385
  } else
325
386
  // This error handling code runs if the connection
@@ -426,21 +487,23 @@ async function handle_fetch_stream (stream, cb, on_bytes) {
426
487
  var {done, value} = await reader.read()
427
488
  }
428
489
  catch (e) {
429
- cb(null, e)
490
+ await cb(null, e)
430
491
  return
431
492
  }
432
493
 
433
494
  // Check if this connection has been closed!
434
495
  if (done) {
435
496
  console.debug("Connection closed.")
436
- cb(null, 'Connection closed')
497
+ await cb(null, 'Connection closed')
437
498
  return
438
499
  }
439
500
 
440
501
  on_bytes?.(value)
441
502
 
442
503
  // Tell the parser to process some more stream
443
- parser.read(value)
504
+ await parser.read(value)
505
+ if (parser.state.result === 'error')
506
+ return await cb(null, new Error(parser.state.message))
444
507
  }
445
508
  }
446
509
 
@@ -459,7 +522,7 @@ var subscription_parser = (cb) => ({
459
522
 
460
523
  // You give it new input information as soon as you get it, and it will
461
524
  // report back with new versions as soon as it finds them.
462
- read (input) {
525
+ async read (input) {
463
526
 
464
527
  // Store the new input!
465
528
  for (let x of input) this.state.input.push(x)
@@ -471,7 +534,7 @@ var subscription_parser = (cb) => ({
471
534
  try {
472
535
  this.state = parse_update (this.state)
473
536
  } catch (e) {
474
- this.cb(null, e)
537
+ await this.cb(null, e)
475
538
  return
476
539
  }
477
540
 
@@ -502,20 +565,20 @@ var subscription_parser = (cb) => ({
502
565
  })
503
566
  }
504
567
 
568
+ // Reset the parser for the next version!
569
+ this.state = {input: this.state.input}
570
+
505
571
  try {
506
- this.cb(update)
572
+ await this.cb(update)
507
573
  } catch (e) {
508
- this.cb(null, e)
574
+ await this.cb(null, e)
509
575
  return
510
576
  }
511
-
512
- // Reset the parser for the next version!
513
- this.state = {input: this.state.input}
514
577
  }
515
578
 
516
579
  // Or maybe there's an error to report upstream
517
580
  else if (this.state.result === 'error') {
518
- this.cb(null, this.state.message)
581
+ await this.cb(null, this.state.message)
519
582
  return
520
583
  }
521
584
 
@@ -585,7 +648,8 @@ function parse_headers (input) {
585
648
  }
586
649
 
587
650
  // Extract the header string
588
- var headers_source = input.slice(start, end).map(x => String.fromCharCode(x)).join('')
651
+ var headers_source = input.slice(start, end)
652
+ headers_source = Array.isArray(headers_source) ? headers_source.map(x => String.fromCharCode(x)).join('') : new TextDecoder().decode(headers_source)
589
653
 
590
654
  // Convert "HTTP 200 OK" to a :status: 200 header
591
655
  headers_source = headers_source.replace(/^HTTP\/?\d*\.?\d* (\d\d\d).*\r?\n/,
@@ -776,6 +840,255 @@ function parse_body (state) {
776
840
  }
777
841
  }
778
842
 
843
+ // multiplex_fetch provides a fetch-like experience for HTTP requests
844
+ // where the result is actually being sent over a separate multiplexed connection.
845
+ //
846
+ // This function assumes a header in params called 'multiplexer' with a value
847
+ // that looks like /multiplexer_id/stream_id. It creates a multiplexer if it
848
+ // doesn't exist already, then performs a fetch providing the multiplexer header.
849
+ // This tells the server to send the results to the given multiplexer.
850
+ //
851
+ async function multiplex_fetch(url, params) {
852
+ // extract multiplexer id from the header
853
+ var multiplexer = params.headers.get('multiplexer').split('/')[1]
854
+
855
+ // create a new multiplexer if it doesn't exist
856
+ if (!multiplex_fetch.multiplexers) multiplex_fetch.multiplexers = {}
857
+ if (!multiplex_fetch.multiplexers[multiplexer]) multiplex_fetch.multiplexers[multiplexer] = (async () => {
858
+ var origin = url[0] === '/' ? location.origin : new URL(url).origin
859
+
860
+ // attempt to establish a multiplexed connection
861
+ try {
862
+ var r = await braid_fetch(`${origin}/${multiplexer}`, {method: 'MULTIPLEX', retry: true})
863
+ } catch (e) {
864
+ // fallback to normal fetch if multiplexed connection fails
865
+ console.error(`Could not establish multiplexed connection.\nGot error: ${e}.\nFalling back to normal connection.`)
866
+ return (url, params) => {
867
+ params.headers.delete('multiplexer')
868
+ return normal_fetch(url, params)
869
+ }
870
+ }
871
+
872
+ // parse the multiplexed stream,
873
+ // and send messages to the appropriate streams
874
+ var streams = new Map()
875
+ var mux_error = null
876
+ parse_multiplex_stream(r.body.getReader(), (stream, bytes) => {
877
+ streams.get(stream)?.(bytes)
878
+ }, e => {
879
+ // the multiplexer stream has died.. let everyone know..
880
+ mux_error = e
881
+ for (var f of streams.values()) f()
882
+ delete multiplex_fetch.multiplexers[multiplexer]
883
+ })
884
+
885
+ // return a "fetch" for this multiplexer
886
+ return async (url, params) => {
887
+ // extract stream id from the header
888
+ var stream = params.headers.get('multiplexer').split('/')[2]
889
+
890
+ // setup a way to receive incoming data from the multiplexer
891
+ var buffers = []
892
+ var bytes_available = () => {}
893
+ var stream_error = null
894
+
895
+ // this utility calls the callback whenever new data is available to process
896
+ async function process_buffers(cb) {
897
+ while (true) {
898
+ // wait for data if none is available
899
+ if (!mux_error && !stream_error && !buffers.length)
900
+ await new Promise(done => bytes_available = done)
901
+ if (mux_error || stream_error) throw (mux_error || stream_error)
902
+
903
+ // process the data
904
+ let ret = cb()
905
+ if (ret) return ret
906
+ }
907
+ }
908
+
909
+ // tell the multiplexer to send bytes for this stream to us
910
+ streams.set(stream, bytes => {
911
+ if (!bytes) {
912
+ streams.delete(stream)
913
+ buffers.push(bytes)
914
+ } else if (!mux_error) buffers.push(bytes)
915
+ bytes_available()
916
+ })
917
+
918
+ // prepare a function that we'll call to cleanly tear things down
919
+ var unset = async e => {
920
+ unset = () => {}
921
+ streams.delete(stream)
922
+ stream_error = e
923
+ bytes_available()
924
+ try {
925
+ await braid_fetch(`${origin}${params.headers.get('multiplexer')}`, {method: 'MULTIPLEX', retry: true})
926
+ } catch (e) {
927
+ console.error(`Could not cancel multiplexed connection:`, e)
928
+ throw e
929
+ }
930
+ }
931
+
932
+ // do the underlying fetch
933
+ try {
934
+ var res = await normal_fetch(url, params)
935
+ if (res.status !== 293) throw new Error('Could not establish multiplexed stream ' + params.headers.get('multiplexer') + ' got status: ' + res.status)
936
+
937
+ // we want to present the illusion that the connection is still open,
938
+ // and therefor closable with "abort",
939
+ // so we handle the abort ourselves to close the multiplexed stream
940
+ params.signal?.addEventListener('abort', () =>
941
+ unset(create_abort_error('stream aborted')))
942
+
943
+ // first, we need to listen for the headers..
944
+ var headers_buffer = new Uint8Array()
945
+ var parsed_headers = await process_buffers(() => {
946
+ // check if the stream has been closed
947
+ var stream_ended = !buffers[buffers.length - 1]
948
+ if (stream_ended) buffers.pop()
949
+
950
+ // aggregate all the new buffers into our big headers_buffer
951
+ headers_buffer = concat_buffers([headers_buffer, ...buffers])
952
+ buffers = []
953
+
954
+ // and if the stream had ended, put that information back
955
+ if (stream_ended) buffers.push(null)
956
+
957
+ // try parsing what we got so far as headers..
958
+ var x = parse_headers(headers_buffer)
959
+
960
+ // how did it go?
961
+ if (x.result === 'error') {
962
+ // if we got an error, give up
963
+ console.log(`headers_buffer: ` + new TextDecoder().decode(headers_buffer))
964
+ throw new Error('error parsing headers')
965
+ } else if (x.result === 'waiting') {
966
+ if (stream_ended) throw new Error('Multiplexed stream ended before headers received.')
967
+ } else return x
968
+ })
969
+
970
+ // put the bytes left over from the header back
971
+ if (parsed_headers.input.length) buffers.unshift(parsed_headers.input)
972
+
973
+ // these headers will also have the status,
974
+ // but we want to present the status in a more usual way below
975
+ var status = parsed_headers.headers[':status']
976
+ delete parsed_headers.headers[':status']
977
+
978
+ // create our own fake response object,
979
+ // to mimik fetch's response object,
980
+ // feeding the user our stream data from the multiplexer
981
+ var res = new Response(new ReadableStream({
982
+ async start(controller) {
983
+ try {
984
+ await process_buffers(() => {
985
+ var b = buffers.shift()
986
+ if (!b) return true
987
+ controller.enqueue(b)
988
+ })
989
+ } finally { controller.close() }
990
+ }
991
+ }), {
992
+ status,
993
+ headers: parsed_headers.headers
994
+ })
995
+
996
+ // add a convenience property for the user to know if
997
+ // this response is being multiplexed
998
+ res.multiplexer = params.headers.get('multiplexer')
999
+
1000
+ // return the fake response object
1001
+ return res
1002
+ } catch (e) {
1003
+ // if we had an error, be sure to unregister ourselves
1004
+ await unset(e)
1005
+ throw e
1006
+ }
1007
+ }
1008
+ })()
1009
+
1010
+ // call the special fetch function for the multiplexer
1011
+ return await (await multiplex_fetch.multiplexers[multiplexer])(url, params)
1012
+ }
1013
+
1014
+ // waits on reader for chunks like: 123 bytes for stream ABC\r\n..123 bytes..
1015
+ // which would trigger cb("ABC", bytes)
1016
+ async function parse_multiplex_stream(reader, cb, on_error) {
1017
+ try {
1018
+ var buffers = [new Uint8Array(0)]
1019
+ var buffers_size = 0
1020
+ var chunk_size = null
1021
+ var stream_id = null
1022
+ var header_length = 0
1023
+ var header_started = false
1024
+
1025
+ while (true) {
1026
+ var { done, value } = await reader.read()
1027
+ if (done) throw new Error('multiplex stream ended unexpectedly')
1028
+ buffers.push(value)
1029
+ buffers_size += value.length
1030
+
1031
+ while (true) {
1032
+ if (chunk_size === null && buffers_size) {
1033
+ if (buffers.length > 1) buffers = [concat_buffers(buffers)]
1034
+
1035
+ var headerComplete = false
1036
+ while (buffers[0].length > header_length) {
1037
+ const byte = buffers[0][header_length]
1038
+ header_length++
1039
+
1040
+ if (byte !== 13 && byte !== 10) header_started = true
1041
+ if (header_started && byte === 10) {
1042
+ headerComplete = true
1043
+ break
1044
+ }
1045
+ }
1046
+ if (headerComplete) {
1047
+ var headerStr = new TextDecoder().decode(buffers[0].slice(0, header_length))
1048
+ var m = headerStr.match(/^[\r\n]*((\d+) bytes for|close) stream ([A-Za-z0-9_-]+)\r\n$/)
1049
+ if (!m) throw new Error('invalid multiplex header')
1050
+ stream_id = m[3]
1051
+
1052
+ buffers[0] = buffers[0].slice(header_length)
1053
+ buffers_size -= header_length
1054
+
1055
+ if (m[1] === 'close') {
1056
+ cb(stream_id)
1057
+ break
1058
+ } else chunk_size = 1 * m[2]
1059
+ } else break
1060
+ } else if (chunk_size !== null && buffers_size >= chunk_size) {
1061
+ if (buffers.length > 1) buffers = [concat_buffers(buffers)]
1062
+
1063
+ var chunk = buffers[0].slice(0, chunk_size)
1064
+ buffers[0] = buffers[0].slice(chunk_size)
1065
+ buffers_size -= chunk_size
1066
+
1067
+ // console.log(`stream_id: ${stream_id}, ${new TextDecoder().decode(chunk)}`)
1068
+
1069
+ cb(stream_id, chunk)
1070
+
1071
+ chunk_size = null
1072
+ stream_id = null
1073
+ header_length = 0
1074
+ header_started = false
1075
+ } else break
1076
+ }
1077
+ }
1078
+ } catch (e) { on_error(e) }
1079
+ }
1080
+
1081
+ // concatenates an array of Uint8Array's, into a single one
1082
+ function concat_buffers(buffers) {
1083
+ const x = new Uint8Array(buffers.reduce((a, b) => a + b.length, 0))
1084
+ let offset = 0
1085
+ for (const b of buffers) {
1086
+ x.set(b, offset)
1087
+ offset += b.length
1088
+ }
1089
+ return x
1090
+ }
1091
+
779
1092
  // The "extra_headers" field is returned to the client on any *update* or
780
1093
  // *patch* to include any headers that we've received, but don't have braid
781
1094
  // semantics for.
@@ -817,6 +1130,12 @@ function ascii_ify(s) {
817
1130
  return s.replace(/[^\x20-\x7E]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
818
1131
  }
819
1132
 
1133
+ function create_abort_error(msg) {
1134
+ var e = new Error(msg)
1135
+ e.name = 'AbortError'
1136
+ return e
1137
+ }
1138
+
820
1139
  // ****************************
821
1140
  // Exports
822
1141
  // ****************************
@@ -230,8 +230,7 @@ function braidify (req, res, next) {
230
230
  // Extract braid info from headers
231
231
  var version = ('version' in req.headers) && JSON.parse('['+req.headers.version+']'),
232
232
  parents = ('parents' in req.headers) && JSON.parse('['+req.headers.parents+']'),
233
- peer = req.headers['peer'],
234
- url = req.url.substr(1)
233
+ peer = req.headers['peer']
235
234
 
236
235
  // Parse the subscribe header
237
236
  var subscribe = req.headers.subscribe
@@ -243,6 +242,151 @@ function braidify (req, res, next) {
243
242
  req.parents = parents
244
243
  req.subscribe = subscribe
245
244
 
245
+ // Multiplexer stuff
246
+ if (braidify.use_multiplexing && req.method === 'MULTIPLEX') {
247
+ // parse the multiplexer id and stream id from the url
248
+ var [multiplexer, stream] = req.url.slice(1).split('/')
249
+
250
+ // if there's just a multiplexer, then we're creating a multiplexer..
251
+ if (!stream) {
252
+ // maintain a Map of all the multiplexers
253
+ if (!braidify.multiplexers) braidify.multiplexers = new Map()
254
+ braidify.multiplexers.set(multiplexer, {streams: new Map(), res})
255
+
256
+ // when the response closes,
257
+ // let everyone know the multiplexer has died
258
+ res.on('close', () => {
259
+ for (var f of braidify.multiplexers.get(multiplexer).streams.values()) f()
260
+ braidify.multiplexers.delete(multiplexer)
261
+ })
262
+
263
+ // keep the connection open,
264
+ // so people can send multiplexed data to it
265
+ res.writeHead(200, {
266
+ 'Cache-Control': 'no-cache',
267
+ 'X-Accel-Buffering': 'no',
268
+ ...req.httpVersion !== '2.0' && {'Connection': 'keep-alive'}
269
+ })
270
+
271
+ // but write something.. won't interfere with stream,
272
+ // and helps flush the headers
273
+ return res.write(`\r\n`)
274
+ } else {
275
+ // in this case, we're closing the given stream
276
+ var m = braidify.multiplexers?.get(multiplexer)
277
+
278
+ // if the multiplexer doesn't exist, send an error
279
+ if (!m) {
280
+ var msg = `multiplexer ${multiplexer} does not exist`
281
+ res.writeHead(400, {'Content-Type': 'text/plain'})
282
+ res.end(msg)
283
+ return
284
+ }
285
+
286
+ // remove this stream, and notify it
287
+ let s = m.streams.get(stream)
288
+ if (s) {
289
+ s()
290
+ m.streams.delete(stream)
291
+ } else m.streams.set(stream, 'abort')
292
+
293
+ // let the requester know we succeeded
294
+ res.writeHead(200, {})
295
+ return res.end(``)
296
+ }
297
+ }
298
+
299
+ // a multiplexer header means the user wants to send the
300
+ // results of this request to the provided multiplexer,
301
+ // tagged with the given stream id
302
+ if (braidify.use_multiplexing && req.headers.multiplexer) {
303
+ // parse the multiplexer id and stream id from the url
304
+ var [multiplexer, stream] = req.headers.multiplexer.slice(1).split('/')
305
+
306
+ var end_things = (msg) => {
307
+ res.statusCode = 400
308
+ res.end(msg)
309
+ }
310
+
311
+ // find the multiplexer object (contains a response object)
312
+ var m = braidify.multiplexers?.get(multiplexer)
313
+ if (!m) return end_things(`multiplexer ${multiplexer} does not exist`)
314
+
315
+ // special case: check that this stream isn't already aborted
316
+ if (m.streams.get(stream) === 'abort') {
317
+ m.streams.delete(stream)
318
+ return end_things(`multiplexer stream ${req.headers.multiplexer} already aborted`)
319
+ }
320
+
321
+ // let the requester know we've multiplexed their response
322
+ res.writeHead(293, {multiplexer: req.headers.multiplexer})
323
+ res.end('Ok.')
324
+
325
+ // and now set things up so that future use of the
326
+ // response object forwards stuff into the multiplexer
327
+
328
+ // first we create a kind of fake socket
329
+ class MultiplexedWritable extends require('stream').Writable {
330
+ constructor(multiplexer, stream) {
331
+ super()
332
+ this.multiplexer = multiplexer
333
+ this.stream = stream
334
+ }
335
+
336
+ _write(chunk, encoding, callback) {
337
+ var len = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk, encoding)
338
+ this.multiplexer.res.write(`${len} bytes for stream ${this.stream}\r\n`)
339
+ this.multiplexer.res.write(chunk, encoding, callback)
340
+
341
+ // console.log(`wrote:`)
342
+ // console.log(`${len} bytes for stream /${this.stream}\r\n`)
343
+ // if (Buffer.isBuffer(chunk)) console.log(new TextDecoder().decode(chunk))
344
+ // else console.log('STRING?: ' + chunk)
345
+ }
346
+ }
347
+ var mw = new MultiplexedWritable(m, stream)
348
+
349
+ // then we create a fake server response,
350
+ // that pipes data to our fake socket
351
+ var res2 = new (require('http').ServerResponse)({})
352
+ res2.useChunkedEncodingByDefault = false
353
+ res2.assignSocket(mw)
354
+
355
+ // register a handler for when the multiplexer closes,
356
+ // to close our fake response stream
357
+ m.streams.set(stream, () => res2.destroy())
358
+
359
+ // when our fake response is done,
360
+ // we want to send a special message to the multiplexer saying so
361
+ res2.on('finish', () => m.res.write(`close stream ${stream}\r\n`))
362
+
363
+ // we want access to "res" to be forwarded to our fake "res2",
364
+ // so that it goes into the multiplexer
365
+ function* get_props(obj) {
366
+ do {
367
+ for (var x of Object.getOwnPropertyNames(obj)) yield x
368
+ } while (obj = Object.getPrototypeOf(obj))
369
+ }
370
+ for (let key of get_props(res)) {
371
+ if (key === '_events' || key === 'emit') continue
372
+ if (res2[key] === undefined) continue
373
+ var value = res[key]
374
+ if (typeof value === 'function') {
375
+ res[key] = res2[key].bind(res2)
376
+ } else {
377
+ +((key) => {
378
+ Object.defineProperty(res, key, {
379
+ get: () => res2[key],
380
+ set: x => res2[key] = x
381
+ })
382
+ })(key)
383
+ }
384
+ }
385
+
386
+ // this is provided so code can know if the response has been multiplexed
387
+ res.multiplexer = res2
388
+ }
389
+
246
390
  // Add the braidly request/response helper methods
247
391
  res.sendUpdate = (stuff) => send_update(res, stuff, req.url, peer)
248
392
  res.sendVersion = res.sendUpdate
@@ -275,7 +419,7 @@ function braidify (req, res, next) {
275
419
 
276
420
  // We have a subscription!
277
421
  res.statusCode = 209
278
- res.setHeader("subscribe", req.headers.subscribe)
422
+ res.setHeader("subscribe", req.headers.subscribe ?? 'true')
279
423
  res.setHeader('cache-control', 'no-cache, no-transform')
280
424
 
281
425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-http",
3
- "version": "1.3.15",
3
+ "version": "1.3.17",
4
4
  "description": "An implementation of Braid-HTTP for Node.js and Browsers",
5
5
  "scripts": {
6
6
  "test": "node test/server.js"