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