braid-http 1.3.52 → 1.3.53

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.
Files changed (2) hide show
  1. package/braid-http-client.js +264 -261
  2. package/package.json +1 -1
@@ -897,8 +897,6 @@ function random_base64url(n) {
897
897
  // multiplex_fetch provides a fetch-like experience for HTTP requests
898
898
  // where the result is actually being sent over a separate multiplexed connection.
899
899
  async function multiplex_fetch(url, params, mux_params, aborter) {
900
- var multiplex_version = '1.0'
901
-
902
900
  var origin = new URL(url, typeof document !== 'undefined' ? document.baseURI : undefined).origin
903
901
 
904
902
  // the mux_key is the same as the origin, unless it is being overriden
@@ -907,278 +905,283 @@ async function multiplex_fetch(url, params, mux_params, aborter) {
907
905
 
908
906
  // create a new multiplexer if it doesn't exist for this origin
909
907
  if (!multiplex_fetch.multiplexers) multiplex_fetch.multiplexers = {}
910
- if (!multiplex_fetch.multiplexers[mux_key]) multiplex_fetch.multiplexers[mux_key] = (
911
- async () => {
912
- // make up a new multiplexer id (unless it is being overriden)
913
- var multiplexer = params.headers.get('multiplex-through')?.split('/')[3]
914
- ?? random_base64url(Math.ceil((mux_params?.id_bits ?? 72) / 6))
915
-
916
- var requests = new Map()
917
- var mux_error = null
918
- var try_deleting = new Set()
919
- var not_used_timeout = null
920
- var mux_aborter = new AbortController()
921
-
922
- function cleanup(e, stay_dead) {
923
- // the multiplexer stream has died.. let everyone know..
924
- mux_error = e
925
- if (!stay_dead) delete multiplex_fetch.multiplexers[mux_key]
926
- for (var f of requests.values()) f()
927
- }
928
-
929
- async function try_deleting_request(request) {
930
- if (!try_deleting.has(request)) {
931
- try_deleting.add(request)
932
- try {
933
- var r = await braid_fetch(`${origin}/.well-known/multiplexer/${multiplexer}/${request}`, {
934
- method: 'DELETE',
935
- headers: { 'Multiplex-Version': multiplex_version },
936
- retry: true
937
- })
908
+ if (!multiplex_fetch.multiplexers[mux_key]) multiplex_fetch.multiplexers[mux_key] =
909
+ create_multiplexer(origin, mux_key, params, mux_params, aborter)
938
910
 
939
- if (!r.ok) throw new Error('status not ok: ' + r.status)
940
- if (r.headers.get('Multiplex-Version') !== multiplex_version)
941
- throw new Error('wrong multiplex version: '
942
- + r.headers.get('Multiplex-Version')
943
- + ', expected ' + multiplex_version)
944
- } catch (e) {
945
- e = new Error(`Could not cancel multiplexed request: ${e}`)
946
- console.error('' + e)
947
- throw e
948
- } finally { try_deleting.delete(request) }
949
- }
950
- }
911
+ // call the special fetch function for the multiplexer
912
+ return await (await multiplex_fetch.multiplexers[mux_key])(url, params)
913
+ }
951
914
 
952
- var mux_promise = (async () => {
953
- // attempt to establish a multiplexed connection
954
- try {
955
- if (mux_params?.via === 'POST') throw 'skip multiplex method'
956
- var r = await braid_fetch(`${origin}/${multiplexer}`, {
957
- signal: mux_aborter.signal,
958
- method: 'MULTIPLEX',
959
- headers: {'Multiplex-Version': multiplex_version},
960
- retry: true
961
- })
962
- if (r.status === 409) {
963
- var e = await r.json()
964
- if (e.error === 'Multiplexer already exists')
965
- return cleanup(create_error(e.error, {dont_wait: true}))
966
- }
967
- if (!r.ok || r.headers.get('Multiplex-Version') !== multiplex_version)
968
- throw 'bad'
969
- } catch (e) {
970
- // some servers don't like custom methods,
971
- // so let's try with a well-known url
972
- try {
973
- r = await braid_fetch(`${origin}/.well-known/multiplexer/${multiplexer}`,
974
- {method: 'POST',
975
- signal: mux_aborter.signal,
976
- headers: {'Multiplex-Version': multiplex_version},
977
- retry: true})
978
- if (r.status === 409) {
979
- var e = await r.json()
980
- if (e.error === 'Multiplexer already exists')
981
- return cleanup(create_error(e.error, {dont_wait: true}))
982
- }
983
- if (!r.ok) throw new Error('status not ok: ' + r.status)
984
- if (r.headers.get('Multiplex-Version') !== multiplex_version)
985
- throw new Error('wrong multiplex version: '
986
- + r.headers.get('Multiplex-Version')
987
- + ', expected ' + multiplex_version)
988
- } catch (e) {
989
- // fallback to normal fetch if multiplexed connection fails
990
- console.error(`Could not establish multiplexer.\n`
991
- + `Got error: ${e}.\nFalling back to normal connection.`)
992
- cleanup(e, true)
993
- return false
994
- }
995
- }
915
+ // returns a function with a fetch-like interface that transparently multiplexes the fetch
916
+ async function create_multiplexer(origin, mux_key, params, mux_params, aborter) {
917
+ var multiplex_version = '1.0'
996
918
 
997
- // parse the multiplexed stream,
998
- // and send messages to the appropriate requests
999
- parse_multiplex_stream(r.body.getReader(), async (request, bytes) => {
1000
- if (requests.has(request)) requests.get(request)(bytes)
1001
- else try_deleting_request(request)
1002
- }, e => cleanup(e))
1003
- })()
1004
-
1005
- // return a "fetch" for this multiplexer
1006
- return async (url, params) => {
1007
-
1008
- // if we already know the multiplexer is not working,
1009
- // then fallback to normal fetch
1010
- // (unless the user is specifically asking for multiplexing)
1011
- if ((await promise_done(mux_promise))
1012
- && (await mux_promise) === false
1013
- && !params.headers.get('multiplex-through'))
1014
- return await normal_fetch(url, params)
1015
-
1016
- // make up a new request id (unless it is being overriden)
1017
- var request = params.headers.get('multiplex-through')?.split('/')[4]
1018
- ?? random_base64url(Math.ceil((mux_params?.id_bits ?? 72) / 6))
1019
-
1020
- // add the Multiplex-Through header without affecting the underlying params
1021
- var mux_headers = new Headers(params.headers)
1022
- mux_headers.set('Multiplex-Through', `/.well-known/multiplexer/${multiplexer}/${request}`)
1023
- mux_headers.set('Multiplex-Version', multiplex_version)
1024
- params = {...params, headers: mux_headers}
1025
-
1026
- // setup a way to receive incoming data from the multiplexer
1027
- var buffers = []
1028
- var bytes_available = () => {}
1029
- var request_error = null
1030
-
1031
- // this utility calls the callback whenever new data is available to process
1032
- async function process_buffers(cb) {
1033
- while (true) {
1034
- // wait for data if none is available
1035
- if (!mux_error && !request_error && !buffers.length)
1036
- await new Promise(done => bytes_available = done)
1037
- if (mux_error || request_error) throw (mux_error || request_error)
1038
-
1039
- // process the data
1040
- let ret = cb()
1041
- if (ret) return ret
1042
- }
1043
- }
919
+ // make up a new multiplexer id (unless it is being overriden)
920
+ var multiplexer = params.headers.get('multiplex-through')?.split('/')[3]
921
+ ?? random_base64url(Math.ceil((mux_params?.id_bits ?? 72) / 6))
922
+
923
+ var requests = new Map()
924
+ var mux_error = null
925
+ var try_deleting = new Set()
926
+ var not_used_timeout = null
927
+ var mux_aborter = new AbortController()
928
+
929
+ function cleanup(e, stay_dead) {
930
+ // the multiplexer stream has died.. let everyone know..
931
+ mux_error = e
932
+ if (!stay_dead) delete multiplex_fetch.multiplexers[mux_key]
933
+ for (var f of requests.values()) f()
934
+ }
1044
935
 
1045
- // tell the multiplexer to send bytes for this request to us
1046
- requests.set(request, bytes => {
1047
- if (!bytes) {
1048
- buffers.push(bytes)
1049
- if (mux_error || request_error) aborter.abort()
1050
- } else if (!mux_error) buffers.push(bytes)
1051
- bytes_available()
936
+ async function try_deleting_request(request) {
937
+ if (!try_deleting.has(request)) {
938
+ try_deleting.add(request)
939
+ try {
940
+ var r = await braid_fetch(`${origin}/.well-known/multiplexer/${multiplexer}/${request}`, {
941
+ method: 'DELETE',
942
+ headers: { 'Multiplex-Version': multiplex_version },
943
+ retry: true
1052
944
  })
1053
945
 
1054
- // prepare a function that we'll call to cleanly tear things down
1055
- clearTimeout(not_used_timeout)
1056
- var unset = async e => {
1057
- unset = () => {}
1058
- requests.delete(request)
1059
- if (!requests.size) not_used_timeout = setTimeout(() => mux_aborter.abort(), mux_params?.not_used_timeout ?? 1000 * 20)
1060
- request_error = e
1061
- bytes_available()
1062
- await try_deleting_request(request)
1063
- }
1064
-
1065
- // do the underlying fetch
1066
- try {
1067
- var res = await normal_fetch(url, params)
1068
-
1069
- if (res.status === 409) {
1070
- var e = await res.json()
1071
- if (e.error === 'Request already multiplexed') {
1072
- // the id is already in use,
1073
- // so we want to retry right away with a different id
1074
- throw create_error(e.error, {dont_wait: true})
1075
- }
1076
- }
1077
-
1078
- if (res.status === 424) {
1079
- // the multiplexer isn't there,
1080
- // could be we arrived before the multiplexer,
1081
- // or after it was shutdown;
1082
- // in either case we want to retry right away
1083
- throw create_error('multiplexer not connected', {dont_wait: true})
1084
- }
946
+ if (!r.ok) throw new Error('status not ok: ' + r.status)
947
+ if (r.headers.get('Multiplex-Version') !== multiplex_version)
948
+ throw new Error('wrong multiplex version: '
949
+ + r.headers.get('Multiplex-Version')
950
+ + ', expected ' + multiplex_version)
951
+ } catch (e) {
952
+ e = new Error(`Could not cancel multiplexed request: ${e}`)
953
+ console.error('' + e)
954
+ throw e
955
+ } finally { try_deleting.delete(request) }
956
+ }
957
+ }
1085
958
 
1086
- // if the response says it's ok,
1087
- // but it's is not a multiplexed response,
1088
- // fall back to as if it was a normal fetch
1089
- if (res.ok && res.status !== 293) return res
1090
-
1091
- if (res.status !== 293)
1092
- throw create_error('Could not establish multiplexed request '
1093
- + params.headers.get('multiplex-through')
1094
- + ', got status: ' + res.status,
1095
- { dont_retry: true })
1096
-
1097
- if (res.headers.get('Multiplex-Version') !== multiplex_version)
1098
- throw create_error('Could not establish multiplexed request '
1099
- + params.headers.get('multiplex-through')
1100
- + ', got unknown version: '
1101
- + res.headers.get('Multiplex-Version'),
1102
- { dont_retry: true })
1103
-
1104
- // we want to present the illusion that the connection is still open,
1105
- // and therefor closable with "abort",
1106
- // so we handle the abort ourselves to close the multiplexed request
1107
- params.signal.addEventListener('abort', () =>
1108
- unset(create_error('request aborted', {name: 'AbortError'})))
1109
-
1110
- // first, we need to listen for the headers..
1111
- var headers_buffer = new Uint8Array()
1112
- var parsed_headers = await process_buffers(() => {
1113
- // check if the request has been closed
1114
- var request_ended = !buffers[buffers.length - 1]
1115
- if (request_ended) buffers.pop()
1116
-
1117
- // aggregate all the new buffers into our big headers_buffer
1118
- headers_buffer = concat_buffers([headers_buffer, ...buffers])
1119
- buffers = []
1120
-
1121
- // and if the request had ended, put that information back
1122
- if (request_ended) buffers.push(null)
1123
-
1124
- // try parsing what we got so far as headers..
1125
- var x = parse_headers(headers_buffer)
1126
-
1127
- // how did it go?
1128
- if (x.result === 'error') {
1129
- // if we got an error, give up
1130
- console.log(`headers_buffer: ` + new TextDecoder().decode(headers_buffer))
1131
- throw new Error('error parsing headers')
1132
- } else if (x.result === 'waiting') {
1133
- if (request_ended)
1134
- throw new Error('Multiplexed request ended before headers received.')
1135
- } else return x
1136
- })
959
+ var mux_promise = (async () => {
960
+ // attempt to establish a multiplexed connection
961
+ try {
962
+ if (mux_params?.via === 'POST') throw 'skip multiplex method'
963
+ var r = await braid_fetch(`${origin}/${multiplexer}`, {
964
+ signal: mux_aborter.signal,
965
+ method: 'MULTIPLEX',
966
+ headers: {'Multiplex-Version': multiplex_version},
967
+ retry: true
968
+ })
969
+ if (r.status === 409) {
970
+ var e = await r.json()
971
+ if (e.error === 'Multiplexer already exists')
972
+ return cleanup(create_error(e.error, {dont_wait: true}))
973
+ }
974
+ if (!r.ok || r.headers.get('Multiplex-Version') !== multiplex_version)
975
+ throw 'bad'
976
+ } catch (e) {
977
+ // some servers don't like custom methods,
978
+ // so let's try with a well-known url
979
+ try {
980
+ r = await braid_fetch(`${origin}/.well-known/multiplexer/${multiplexer}`,
981
+ {method: 'POST',
982
+ signal: mux_aborter.signal,
983
+ headers: {'Multiplex-Version': multiplex_version},
984
+ retry: true})
985
+ if (r.status === 409) {
986
+ var e = await r.json()
987
+ if (e.error === 'Multiplexer already exists')
988
+ return cleanup(create_error(e.error, {dont_wait: true}))
989
+ }
990
+ if (!r.ok) throw new Error('status not ok: ' + r.status)
991
+ if (r.headers.get('Multiplex-Version') !== multiplex_version)
992
+ throw new Error('wrong multiplex version: '
993
+ + r.headers.get('Multiplex-Version')
994
+ + ', expected ' + multiplex_version)
995
+ } catch (e) {
996
+ // fallback to normal fetch if multiplexed connection fails
997
+ console.error(`Could not establish multiplexer.\n`
998
+ + `Got error: ${e}.\nFalling back to normal connection.`)
999
+ cleanup(e, true)
1000
+ return false
1001
+ }
1002
+ }
1137
1003
 
1138
- // put the bytes left over from the header back
1139
- if (parsed_headers.input.length) buffers.unshift(parsed_headers.input)
1140
-
1141
- // these headers will also have the status,
1142
- // but we want to present the status in a more usual way below
1143
- var status = parsed_headers.headers[':status']
1144
- delete parsed_headers.headers[':status']
1145
-
1146
- // create our own fake response object,
1147
- // to mimik fetch's response object,
1148
- // feeding the user our request data from the multiplexer
1149
- var res = new Response(new ReadableStream({
1150
- async start(controller) {
1151
- try {
1152
- await process_buffers(() => {
1153
- var b = buffers.shift()
1154
- if (!b) return true
1155
- controller.enqueue(b)
1156
- })
1157
- } catch (e) {
1158
- controller.error(e)
1159
- } finally { controller.close() }
1160
- }
1161
- }), {
1162
- status,
1163
- headers: parsed_headers.headers
1164
- })
1004
+ // parse the multiplexed stream,
1005
+ // and send messages to the appropriate requests
1006
+ parse_multiplex_stream(r.body.getReader(), async (request, bytes) => {
1007
+ if (requests.has(request)) requests.get(request)(bytes)
1008
+ else try_deleting_request(request)
1009
+ }, e => cleanup(e))
1010
+ })()
1011
+
1012
+ // return a "fetch" for this multiplexer
1013
+ return async (url, params) => {
1014
+
1015
+ // if we already know the multiplexer is not working,
1016
+ // then fallback to normal fetch
1017
+ // (unless the user is specifically asking for multiplexing)
1018
+ if ((await promise_done(mux_promise))
1019
+ && (await mux_promise) === false
1020
+ && !params.headers.get('multiplex-through'))
1021
+ return await normal_fetch(url, params)
1022
+
1023
+ // make up a new request id (unless it is being overriden)
1024
+ var request = params.headers.get('multiplex-through')?.split('/')[4]
1025
+ ?? random_base64url(Math.ceil((mux_params?.id_bits ?? 72) / 6))
1026
+
1027
+ // add the Multiplex-Through header without affecting the underlying params
1028
+ var mux_headers = new Headers(params.headers)
1029
+ mux_headers.set('Multiplex-Through', `/.well-known/multiplexer/${multiplexer}/${request}`)
1030
+ mux_headers.set('Multiplex-Version', multiplex_version)
1031
+ params = {...params, headers: mux_headers}
1032
+
1033
+ // setup a way to receive incoming data from the multiplexer
1034
+ var buffers = []
1035
+ var bytes_available = () => {}
1036
+ var request_error = null
1037
+
1038
+ // this utility calls the callback whenever new data is available to process
1039
+ async function process_buffers(cb) {
1040
+ while (true) {
1041
+ // wait for data if none is available
1042
+ if (!mux_error && !request_error && !buffers.length)
1043
+ await new Promise(done => bytes_available = done)
1044
+ if (mux_error || request_error) throw (mux_error || request_error)
1045
+
1046
+ // process the data
1047
+ let ret = cb()
1048
+ if (ret) return ret
1049
+ }
1050
+ }
1165
1051
 
1166
- // add a convenience property for the user to know if
1167
- // this response is being multiplexed
1168
- res.is_multiplexed = true
1052
+ // tell the multiplexer to send bytes for this request to us
1053
+ requests.set(request, bytes => {
1054
+ if (!bytes) {
1055
+ buffers.push(bytes)
1056
+ if (mux_error || request_error) aborter.abort()
1057
+ } else if (!mux_error) buffers.push(bytes)
1058
+ bytes_available()
1059
+ })
1060
+
1061
+ // prepare a function that we'll call to cleanly tear things down
1062
+ clearTimeout(not_used_timeout)
1063
+ var unset = async e => {
1064
+ unset = () => {}
1065
+ requests.delete(request)
1066
+ if (!requests.size) not_used_timeout = setTimeout(() => mux_aborter.abort(), mux_params?.not_used_timeout ?? 1000 * 20)
1067
+ request_error = e
1068
+ bytes_available()
1069
+ await try_deleting_request(request)
1070
+ }
1169
1071
 
1170
- // return the fake response object
1171
- return res
1172
- } catch (e) {
1173
- // if we had an error, be sure to unregister ourselves
1174
- unset(e)
1175
- throw mux_error || e
1072
+ // do the underlying fetch
1073
+ try {
1074
+ var res = await normal_fetch(url, params)
1075
+
1076
+ if (res.status === 409) {
1077
+ var e = await res.json()
1078
+ if (e.error === 'Request already multiplexed') {
1079
+ // the id is already in use,
1080
+ // so we want to retry right away with a different id
1081
+ throw create_error(e.error, {dont_wait: true})
1176
1082
  }
1177
1083
  }
1178
- })()
1179
1084
 
1180
- // call the special fetch function for the multiplexer
1181
- return await (await multiplex_fetch.multiplexers[mux_key])(url, params)
1085
+ if (res.status === 424) {
1086
+ // the multiplexer isn't there,
1087
+ // could be we arrived before the multiplexer,
1088
+ // or after it was shutdown;
1089
+ // in either case we want to retry right away
1090
+ throw create_error('multiplexer not connected', {dont_wait: true})
1091
+ }
1092
+
1093
+ // if the response says it's ok,
1094
+ // but it's is not a multiplexed response,
1095
+ // fall back to as if it was a normal fetch
1096
+ if (res.ok && res.status !== 293) return res
1097
+
1098
+ if (res.status !== 293)
1099
+ throw create_error('Could not establish multiplexed request '
1100
+ + params.headers.get('multiplex-through')
1101
+ + ', got status: ' + res.status,
1102
+ { dont_retry: true })
1103
+
1104
+ if (res.headers.get('Multiplex-Version') !== multiplex_version)
1105
+ throw create_error('Could not establish multiplexed request '
1106
+ + params.headers.get('multiplex-through')
1107
+ + ', got unknown version: '
1108
+ + res.headers.get('Multiplex-Version'),
1109
+ { dont_retry: true })
1110
+
1111
+ // we want to present the illusion that the connection is still open,
1112
+ // and therefor closable with "abort",
1113
+ // so we handle the abort ourselves to close the multiplexed request
1114
+ params.signal.addEventListener('abort', () =>
1115
+ unset(create_error('request aborted', {name: 'AbortError'})))
1116
+
1117
+ // first, we need to listen for the headers..
1118
+ var headers_buffer = new Uint8Array()
1119
+ var parsed_headers = await process_buffers(() => {
1120
+ // check if the request has been closed
1121
+ var request_ended = !buffers[buffers.length - 1]
1122
+ if (request_ended) buffers.pop()
1123
+
1124
+ // aggregate all the new buffers into our big headers_buffer
1125
+ headers_buffer = concat_buffers([headers_buffer, ...buffers])
1126
+ buffers = []
1127
+
1128
+ // and if the request had ended, put that information back
1129
+ if (request_ended) buffers.push(null)
1130
+
1131
+ // try parsing what we got so far as headers..
1132
+ var x = parse_headers(headers_buffer)
1133
+
1134
+ // how did it go?
1135
+ if (x.result === 'error') {
1136
+ // if we got an error, give up
1137
+ console.log(`headers_buffer: ` + new TextDecoder().decode(headers_buffer))
1138
+ throw new Error('error parsing headers')
1139
+ } else if (x.result === 'waiting') {
1140
+ if (request_ended)
1141
+ throw new Error('Multiplexed request ended before headers received.')
1142
+ } else return x
1143
+ })
1144
+
1145
+ // put the bytes left over from the header back
1146
+ if (parsed_headers.input.length) buffers.unshift(parsed_headers.input)
1147
+
1148
+ // these headers will also have the status,
1149
+ // but we want to present the status in a more usual way below
1150
+ var status = parsed_headers.headers[':status']
1151
+ delete parsed_headers.headers[':status']
1152
+
1153
+ // create our own fake response object,
1154
+ // to mimik fetch's response object,
1155
+ // feeding the user our request data from the multiplexer
1156
+ var res = new Response(new ReadableStream({
1157
+ async start(controller) {
1158
+ try {
1159
+ await process_buffers(() => {
1160
+ var b = buffers.shift()
1161
+ if (!b) return true
1162
+ controller.enqueue(b)
1163
+ })
1164
+ } catch (e) {
1165
+ controller.error(e)
1166
+ } finally { controller.close() }
1167
+ }
1168
+ }), {
1169
+ status,
1170
+ headers: parsed_headers.headers
1171
+ })
1172
+
1173
+ // add a convenience property for the user to know if
1174
+ // this response is being multiplexed
1175
+ res.is_multiplexed = true
1176
+
1177
+ // return the fake response object
1178
+ return res
1179
+ } catch (e) {
1180
+ // if we had an error, be sure to unregister ourselves
1181
+ unset(e)
1182
+ throw mux_error || e
1183
+ }
1184
+ }
1182
1185
  }
1183
1186
 
1184
1187
  // waits on reader for chunks like: 123 bytes for request ABC\r\n..123 bytes..
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-http",
3
- "version": "1.3.52",
3
+ "version": "1.3.53",
4
4
  "description": "An implementation of Braid-HTTP for Node.js and Browsers",
5
5
  "scripts": {
6
6
  "test": "node test/server.js"