diff --git a/pkg/arvo/sys/lull.hoon b/pkg/arvo/sys/lull.hoon index e071b53909..de8b7bef4c 100644 --- a/pkg/arvo/sys/lull.hoon +++ b/pkg/arvo/sys/lull.hoon @@ -3085,6 +3085,9 @@ :: notification that a cache entry has changed :: [%grow =path] + :: UIP-125 + :: + [%websocket-response wid=@ event=websocket-event] == :: +$ task @@ -3151,7 +3154,28 @@ :: remember (or update) a cache mapping :: [%set-response url=@t entry=(unit cache-entry)] + :: UIP-125 + :: + [%websocket-event ws-id=@ event=websocket-event] + [%websocket-handshake ws-id=@ secure=? =address =request:http] + == + :: UIP-125 + :: + +$ websocket-connection + $: app=term + =inbound-request + == + +$ websocket-message + $: opcode=@ud + message=(unit data=octs) + == + +$ websocket-event + $% [%accept ~] + [%reject ~] + [%disconnect ~] + [%message message=websocket-message] == + :: +origin: request origin as specified in an Origin header :: +$ origin @torigin @@ -3956,6 +3980,10 @@ :: %response: response to the caller :: [%http-response =client-response] + :: UIP-125 + :: + [%websocket-handshake id=@ud url=@t] + [%websocket-response id=@ud websocket-event:eyre] == :: +$ task @@ -3978,6 +4006,24 @@ :: receives http data from outside :: [%receive id=@ud =http-event:http] + :: UIP-125 + :: + [%cancel-websocket id=@ud] + :: receives websocket event from earth + :: + [%websocket-connect app=term url=@t] + :: receives websocket event from earth + :: + [%websocket-event id=@ud event=websocket-event:eyre] + == + :: UIP-125 + :: + +$ websocket-connection + $: app=term + =duct + id=@ud + url=@t + status=?(%pending %accepted) == :: +client-response: one or more client responses given to the caller :: diff --git a/pkg/arvo/sys/vane/eyre.hoon b/pkg/arvo/sys/vane/eyre.hoon index f65610dab2..b5aa57c50a 100644 --- a/pkg/arvo/sys/vane/eyre.hoon +++ b/pkg/arvo/sys/vane/eyre.hoon @@ -43,7 +43,7 @@ ++ axle $: :: date: date at which http-server's state was updated to this data structure :: - date=%~2025.1.31 + date=%~2025.10.28 :: server-state: state of inbound requests :: =server-state @@ -95,6 +95,9 @@ :: who may have been affected by urbit/urbit#7103 :: check-session-timer=_| + :: UIP 125 + :: + sockets=(map @ud websocket-connection) == :: channel-request: an action requested on a channel :: @@ -638,6 +641,24 @@ |= [wid=@u tan=tang] ^- wall (zing (turn tan |=(a=tank (wash 0^wid a)))) +:: +wall-to-octs: text to binary output +:: +++ wall-to-octs + |= =wall + ^- (unit octs) + :: + ?: =(~ wall) + ~ + :: + :- ~ + %- as-octs:mimes:html + %- crip + %- zing ^- ^wall + %- zing ^- (list ^wall) + %+ turn wall + |= t=tape + ^- ^wall + ~[t "\0a"] :: +internal-server-error: 500 page, with a tang :: ++ internal-server-error @@ -802,7 +823,7 @@ =^ ?(invalid=@uv [suv=@uv =identity som=(list move)]) state (session-for-request:authentication request) ?@ - - :: the request provided a session cookie that's not (or no longer) + :: the request provided a session coocokie that's not (or no longer) :: valid. to make sure they're aware, tell them 401 :: ::NOTE some code duplication with below, but request handling deserves @@ -1021,6 +1042,76 @@ %^ return-static-data-on-duct 404 'text/html' (error-page 404 authenticated url.request ~) == + :: WebSockets event + :: + ++ ws-event + |= [wid=@ event=websocket-event] + =/ conn (~(get by connections.state) duct) + :: ~& ws-conn=conn + ?~ conn `state + ?. ?=(%app -.action.u.conn) `state + =/ url url.request.inbound-request.u.conn + =/ pat=(unit (list @t)) + (rush url ;~(pfix fas (more fas smeg:de-purl:html))) + ?~ pat ~&(error-parsing-path=url `state) + =/ app app.action.u.conn + =/ identity identity.u.conn + =/ wsid (scot %ud wid) + :: ~& >> ws-event=[identity app pat wsid] + :: TODO damn how + :: ~& eyre-ws-event=-.event + ?+ -.event `state + %message + :_ state + :~ %+ deal-as + /run-ws-app-request/[wsid] + :^ identity our app + :+ %poke %websocket-server-message + !>([wid u.pat message.event]) + == + :: + %disconnect + =. connections.state (~(del by connections.state) duct) + :_ state + :~ %+ deal-as + /ws-watch-response/[wsid] + [identity our app %leave ~] + == + == + :: + ++ ws-handshake + |= [wid=@ secure=? =address:eyre =request:http] + ^- [(list move) server-state] + =/ host=(unit @t) (get-header:http 'host' header-list.request) + =/ [=action suburl=@t] + (get-action-for-binding host url.request) + :: TODO enable other actions + ?. ?=(%app -.action) `state + :: TODO!! get clear what all the identity thing has to be + =/ app app.action + =^ $@(invalid=@uv [@uv identity (list move)]) state + (session-for-request:authentication request) + =/ [session-id=@uv =identity som=(list move)] + ?^ - - + ~&(invalid-session=invalid [invalid [%fake *@p] ~]) + =/ authenticated ?=(%ours -.identity) + =/ connection=outstanding-connection + [action [authenticated secure address request] [session-id identity] ~ 0] + =. connections.state + (~(put by connections.state) duct connection) + :: eyre-id is assigned way up in this arm + =/ wsid (scot %ud wid) + :_ state + ~& som=som + %+ weld som + :~ %+ deal-as /ws-watch-response/[wsid] + [identity our app %watch /websocket-server/[wsid]] + :: + %+ deal-as /run-ws-app-request/[wsid] + :^ identity our app + :+ %poke %websocket-handshake + !>(`[@ inbound-request:eyre]`[wid inbound-request.connection]) + == :: +handle-ip: respond with the requester's ip :: ++ handle-ip @@ -1046,7 +1137,10 @@ ++ galaxy-for |= =ship ^- @p - (rear (^^saxo:title rof /ames our now ship)) + =/ next (^^sein:title rof /eyre our now ship) + ?: ?=(%czar (clan:title next)) + next + $(ship next) :: ++ handle-sponsor |= [=identity =request:http] @@ -2400,7 +2494,7 @@ :: =/ mode=?(%json %jam) (find-channel-mode %'GET' header-list.request) - =^ [exit=? c=cord moves=(list move)] state + =^ [exit=? =wall moves=(list move)] state :: the request may include a 'Last-Event-Id' header :: =/ maybe-last-event-id=(unit @ud) @@ -2418,7 +2512,7 @@ =^ mos state %^ return-static-data-on-duct 403 'text/html' (error-page 403 | url.request ~) - [[& '' mos] state] + [[& ~ mos] state] :: make sure the request "mode" doesn't conflict with a prior request :: ::TODO or could we change that on the spot, given that only a single @@ -2428,7 +2522,7 @@ %^ return-static-data-on-duct 406 'text/html' =; msg=tape (error-page 406 %.y url.request msg) "channel already established in {(trip mode.channel)} mode" - [[& '' mos] state] + [[& ~ mos] state] :: when opening an event-stream, we must cancel our timeout timer :: if there's no duct already bound. else, kill the old request, :: we will replace its duct at the end of this arm @@ -2457,12 +2551,12 @@ :: :: combine the remaining queued events to send to the client :: - =; event-replay=cord + =; event-replay=wall [[| - cancel-moves] state] - %- roll :_ - |=([a=cord b=cord] (cat 3 a b)) + %- zing + %- flop =/ queue events.channel - =| events=(list cord) + =| events=(list wall) |- ^+ events ?: =(~ queue) @@ -2473,9 +2567,9 @@ :: since conversion failure also gets caught during first receive. :: we can't do anything about this, so consider it unsupported. =/ said - (channel-event-to-cord channel request-id channel-event) + (channel-event-to-tape channel request-id channel-event) ?~ said $ - $(events [(event-cord-to-event-stream id +.u.said) events]) + $(events [(event-tape-to-wall id +.u.said) events]) ?: exit [moves state] :: send the start event to the client :: @@ -2492,7 +2586,7 @@ :: instead. some clients won't consider the connection established :: until they've heard some bytes come over the wire. :: - ?. =(~ c) (some (as-octs:mimes:html c)) + ?. =(~ wall) (wall-to-octs wall) (some (as-octs:mimes:html ':\0a')) :: complete=%.n @@ -2800,8 +2894,8 @@ (sign-to-channel-event sign u.channel request-id) ?~ maybe-channel-event [~ state] =/ =channel-event u.maybe-channel-event - =/ said=(unit (quip move cord)) - (channel-event-to-cord u.channel request-id channel-event) + =/ said=(unit (quip move tape)) + (channel-event-to-tape u.channel request-id channel-event) =? moves ?=(^ said) (weld moves -.u.said) =* sending &(?=([%| *] state.u.channel) ?=(^ said)) @@ -2824,9 +2918,8 @@ :* %response %continue :: ^= data - :- ~ - %- as-octs:mimes:html - (event-cord-to-event-stream next-id +:(need said)) + %- wall-to-octs + (event-tape-to-wall next-id +:(need said)) :: complete=%.n == @@ -2887,10 +2980,9 @@ :* %response %continue :: ^= data - :- ~ - %- as-octs:mimes:html - %+ event-cord-to-event-stream next-id - +:(need (channel-event-to-cord u.channel request-id %kick ~)) + %- wall-to-octs + %+ event-tape-to-wall next-id + +:(need (channel-event-to-tape u.channel request-id %kick ~)) :: complete=%.n == @@ -2924,15 +3016,15 @@ ?. ?=([~ ~ *] des) ((trace 0 |.("no desk for app {}")) ~) `!<(=desk q.u.u.des) - :: +channel-event-to-cord: render channel-event from request-id in specified mode + :: +channel-event-to-tape: render channel-event from request-id in specified mode :: - ++ channel-event-to-cord + ++ channel-event-to-tape |= [=channel request-id=@ud =channel-event] - ^- (unit (quip move cord)) + ^- (unit (quip move tape)) ?- mode.channel %json %+ bind (channel-event-to-json channel request-id channel-event) - |=((quip move json) [+<- (en:json:html +<+)]) - %jam =- `[~ (scot %uw (jam -))] + |=((quip move json) [+<- (trip (en:json:html +<+))]) + %jam =- `[~ (scow %uw (jam -))] [request-id channel-event] == :: +channel-event-to-json: render channel event as json channel event @@ -3005,13 +3097,14 @@ == == :: - ++ event-cord-to-event-stream - ~% %eyre-cord-to-event-stream ..part ~ - |= [event-id=@ud data=cord] - ^- cord - %^ cat 3 - (cat 3 (cat 3 'id: ' (crip (a-co:co event-id))) '\0a') - (cat 3 (cat 3 'data: ' data) '\0a\0a') + ++ event-tape-to-wall + ~% %eyre-tape-to-wall ..part ~ + |= [event-id=@ud =tape] + ^- wall + :~ (weld "id: " (a-co:co event-id)) + (weld "data: " tape) + "" + == :: ++ on-channel-heartbeat |= channel-id=@t @@ -3106,6 +3199,30 @@ tang == [(weld moves-1 moves-2) state] + :: + ++ handle-ws-response + |= [wid=@ event=websocket-event] + ^- [(list move) server-state] + =. connections.state + ?+ -.event connections.state + ?(%reject %disconnect) (~(del by connections.state) duct) + == + =. sockets.state + ?+ -.event sockets.state + ?(%reject %disconnect) + (~(del by sockets.state) wid) + :: + %accept + =/ outstanding (~(get by connections.state) duct) + ?~ outstanding + ~& >>> eyre-ws-error=[wid event] sockets.state + =/ req=inbound-request inbound-request.u.outstanding + :: TODO this is bad + ?> ?=(%app -.action.u.outstanding) + (~(put by sockets.state) wid +.action.u.outstanding req) + == + [[duct %give %websocket-response [wid event]]~ state] + :: :: +handle-response: check a response for correctness and send to earth :: :: All outbound responses including %http-server generated responses need to go @@ -3113,6 +3230,7 @@ :: where we perform logging and state cleanup for connections that we're :: done with. :: + :: ++ handle-response |= =http-event:http ^- [(list move) server-state] @@ -3555,7 +3673,6 @@ ~/ %eyre-call |= [=duct dud=(unit goof) wrapped-task=(hobo task)] ^- [(list move) _http-server-gate] - ~> %spin.['call/eyre'] :: =/ task=task ((harden task) wrapped-task) :: @@ -3853,13 +3970,20 @@ %set-response =^ moves server-state.ax (set-response:server +.task) [moves http-server-gate] + :: + %websocket-event + =^ moves server-state.ax (ws-event:server +.task) + [moves http-server-gate] + :: + %websocket-handshake + =^ moves server-state.ax (ws-handshake:server +.task) + [moves http-server-gate] == :: ++ take ~/ %eyre-take |= [=wire =duct dud=(unit goof) =sign] ^- [(list move) _http-server-gate] - ~> %spin.['take/eyre'] => %= . sign ?: ?=(%gall -.sign) @@ -3880,19 +4004,65 @@ ?+ i.wire ~|([%bad-take-wire wire] !!) :: - %run-app-request run-app-request - %watch-response watch-response - %sessions sessions - %channel channel - %acme acme-ack - %conversion-cache `http-server-gate + %run-app-request run-app-request + %watch-response watch-response + %sessions sessions + %channel channel + %acme acme-ack + %conversion-cache `http-server-gate + %run-ws-app-request run-ws-app-request + %ws-watch-response watch-ws-response == :: + ++ watch-ws-response + =/ event-args [[eny duct now rof] server-state.ax] + ?> ?=([@ *] t.wire) + ?+ sign `http-server-gate + [%gall %unto %watch-ack *] + ?~ p.p.sign + :: received a positive acknowledgment: take no action + :: + [~ http-server-gate] + :: we have an error; propagate it to the client + :: + ~& gall-error=u.p.p.sign + =/ handle-gall-error + handle-gall-error:(per-server-event event-args) + =^ moves server-state.ax (handle-gall-error u.p.p.sign) + [moves http-server-gate] + :: + [%gall %unto %kick ~] + =/ handle-ws-response handle-ws-response:(per-server-event event-args) + =^ moves server-state.ax + :: TODO not great + =/ wids (head (flop wire)) + =/ wid (slav %ud wids) + (handle-ws-response wid [%disconnect ~]) + [moves http-server-gate] + :: + [%gall %unto %fact *] + =/ mark p.cage.p.sign + ?. ?=(%websocket-response mark) + =/ handle-gall-error + handle-gall-error:(per-server-event event-args) + =^ moves server-state.ax + (handle-gall-error leaf+"eyre bad mark {(trip mark)}" ~) + [moves http-server-gate] + =/ event !<([@ websocket-event] q.cage.p.sign) + =/ handle-ws-response handle-ws-response:(per-server-event event-args) + =^ moves server-state.ax + (handle-ws-response event) + [moves http-server-gate] + == + :: + ++ run-ws-app-request `http-server-gate + :: ++ run-app-request :: ?> ?=([%gall %unto *] sign) :: :: + :: ~& run-app-req-eyre=p.sign ?> ?=([%poke-ack *] p.sign) ?> ?=([@ *] t.wire) ?~ p.p.sign @@ -3920,6 +4090,7 @@ [~ http-server-gate] :: we have an error; propagate it to the client :: + ~& eyre-watch-response-gall-error=duct =/ handle-gall-error handle-gall-error:(per-server-event event-args) =^ moves server-state.ax (handle-gall-error u.p.p.sign) @@ -4134,7 +4305,8 @@ [date=%~2023.4.11 server-state-3] [date=%~2023.5.15 server-state-4] [date=%~2024.8.20 server-state-4] - [date=%~2025.1.31 server-state] + [date=%~2025.1.31 server-state-5] + [date=%~2025.10.28 server-state] == :: +$ server-state-0 @@ -4246,11 +4418,25 @@ ports=[insecure=@ud secure=(unit @ud)] outgoing-duct=duct verb=@ + == + :: + +$ server-state-5 + $: bindings=(list [=binding =duct =action]) + cache=(map url=@t [aeon=@ud val=(unit cache-entry)]) + =cors-registry + connections=(map duct outstanding-connection) + auth=authentication-state + =channel-state + domains=(set turf) + =http-config + ports=[insecure=@ud secure=(unit @ud)] + outgoing-duct=duct + verb=@ + check-session-timer=_| == -- |= old=axle-any ^+ http-server-gate - ~> %spin.['load/eyre'] ?- -.old :: :: adds /~/name @@ -4366,8 +4552,16 @@ date.old %~2025.1.31 verb.old [verb.old check-session-timer=&] == + :: + :: adds web sockets: UIP-125 :: %~2025.1.31 + %= $ + date.old %~2025.10.28 + check-session-timer.old [check-session-timer.old sockets=~] + == + :: + %~2025.10.28 http-server-gate(ax old) :: == @@ -4381,7 +4575,6 @@ ^- roon |= [lyc=gang pov=path car=term bem=beam] ^- (unit (unit cage)) - ~> %spin.['scry/eyre'] =* ren car =* why=shop &/p.bem =* syd q.bem diff --git a/pkg/arvo/sys/vane/iris.hoon b/pkg/arvo/sys/vane/iris.hoon index bdcfe1b42d..35b37c6378 100644 --- a/pkg/arvo/sys/vane/iris.hoon +++ b/pkg/arvo/sys/vane/iris.hoon @@ -30,7 +30,16 @@ :: :: $% [%flog =flog:dill] - == == == + == == + :: + :: %g: to gall + :: + $: %g + :: + :: + $% [%deal p=sack q=term r=deal:gall] + == == + == -- :: more structures :: @@ -38,7 +47,7 @@ +$ axle $: :: date: date at which iris state was updated to this data structure :: - date=%~2025.7.17 + date=%~2026.1.1 :: :: =state @@ -46,7 +55,10 @@ :: +state:client: state relating to open outbound HTTP connections :: +$ state - $: :: next-id: monotonically increasing id number for the next connection + $: :: sockets: ongoing websocket connections + :: + sockets=(map @ud websocket-connection) + :: next-id: monotonically increasing id number for the next connection :: next-id=@ud :: connection-by-id: open connections to the @@ -328,6 +340,100 @@ connection-by-id (~(del by connection-by-id.state) id) connection-by-duct (~(del by connection-by-duct.state) duct.u.con) == + :: UIP-125 + :: + ++ ws-connect + |= [desk=term url=@t] + ~& iris-ws-connect=[desk url duct] + ?: (~(has by connection-by-duct.state) duct) + ~& "cant sent second ws-connect on same duct" + `state + :: TODO ... the wid comes from vere tho...? + =^ id next-id.state [next-id.state +(next-id.state)] + =/ wc=websocket-connection [desk duct id url %pending] + =. sockets.state (~(put by sockets.state) id wc) + :: keep track of the duct for cancellation + :: This sends it to Vere to actually do the request + :_ state + [outbound-duct.state %give %websocket-handshake id url]~ + :: + ++ cleanup-ws + |= wid=@ud + ^- [(list move) ^state] + ?~ con=(~(get by sockets.state) wid) + `state + =. sockets.state (~(del by sockets.state) wid) + :_ state + :~ (leave-agent wid app.u.con) + == + :: incoming websockets event to be sent BY VERE NOT USERSPACE + :: + ++ ws-event + |= [wid=@ud event=websocket-event:eyre] + ~& iris-ws-event=[wid -.event duct] + =/ wc (~(get by sockets.state) wid) + ?~ wc `state + =/ wc u.wc + ~& wc=wc + =^ moves state + ?- -.event + %reject (cleanup-ws wid) + %disconnect (cleanup-ws wid) + :: + %accept + =. wc wc(status %accepted) + =. sockets.state (~(put by sockets.state) wid wc) + :_ state + :~ (watch-agent wid app.wc) + == + :: + %message + :_ state + :~ (poke-agent [wid +.event] app.wc) + == + == + =/ m2 (ws-response wc event) + [(welp m2 moves) state] + :: + ++ ws-response + |= [wc=websocket-connection event=websocket-event:eyre] + ^- (list move) + ~& ws-response=wc + :~ :* duct.wc + %give + %websocket-response + id.wc + event + == == + :: + ++ ws-cancel + |= wid=@ud + =. sockets.state (~(del by sockets.state) wid) + :_ state + :: TODO this goes to vere + [outbound-duct.state %give %cancel-request wid]~ + :: + ++ watch-agent + |= [wid=@ud app=term] + ^- move + =/ wids (scot %ud wid) + =/ =note [%g %deal [our our /iris] app %watch /websocket-client/[wids]] + [duct %pass /ws-watch/[wids] note] + :: + ++ leave-agent + |= [wid=@ud app=term] + ^- move + ~& iris-leave-agent=[wid app] + =/ wids (scot %ud wid) + =/ =note [%g %deal [our our /iris] app %leave ~] + [duct %pass /ws-watch/[wids] note] + :: + ++ poke-agent + |= [msg=[@ud websocket-message:eyre] app=term] + ^- move + =/ =note + [%g %deal [our our /iris] app %poke %websocket-client-message !>(msg)] + [duct %pass /iris-ws-poke note] -- -- :: end the =~ @@ -402,15 +508,68 @@ %receive =^ moves state.ax (receive:client +.task) [moves iris-gate] + :: UIP-125 + :: + %websocket-connect + =^ moves state.ax (ws-connect:client +.task) + [moves iris-gate] + :: + %websocket-event + =^ moves state.ax (ws-event:client +.task) + [moves iris-gate] + :: + %cancel-websocket + =^ moves state.ax (ws-cancel:client +.task) + [moves iris-gate] == :: http-client issues no requests to other vanes :: ++ take - |= [=wire =duct dud=(unit goof) sign=*] + |= [wire=(pole knot) =duct dud=(unit goof) hin=sign-arvo] ^- [(list move) _iris-gate] ~> %spin.['take/iris'] + ~& iris-take=[wire duct] ?< ?=(^ dud) - !! + :_ iris-gate + ?+ wire ~ + [%ws-watch wids=@t ~] + =/ wid (slav %ud wids.wire) + ~& iris-ws-take=-.hin + ?+ -.hin ~ + %gall + ?> ?=([%unto *] +.hin) + ~& hin=-.p.hin + ?+ -.p.hin ~ + ?(%poke-ack %watch-ack) + ?~ p.p.hin ~ + ~ + :: + %kick + =/ event-args [[eny duct now rof] state.ax] + =/ client (per-client-event event-args) + =^ movs state.ax (cleanup-ws:client wid) + movs + :: + %fact + =* cag cage.p.hin + :: This comes from agent, goes to vere + ~& > iris-take-ws-fact=p.cag + ?+ p.cag ~&(bad-fact+p.cag !!) + %message + =/ msg !<(websocket-message:eyre q.cag) + [outbound-duct.state.ax %give %websocket-response wid %message msg]~ + :: + %disconnect + ~& iris-take-ws-disconnect=wid + =/ event-args [[eny duct now rof] state.ax] + =/ client (per-client-event event-args) + =^ movs state.ax (cleanup-ws:client wid) + %+ welp movs + [outbound-duct.state.ax %give %websocket-response wid %disconnect ~]~ + == + == + == + == :: ++ iris-gate ..$ :: +load: migrate old state to new state (called on vane reload) @@ -419,7 +578,8 @@ => |% +$ axle-any $% [date=%~2019.2.8 state=state-0] - [date=%~2025.7.17 =state] + [date=%~2025.7.17 state=state-1] + [date=%~2026.1.1 =state] == :: +$ state-0 @@ -436,28 +596,41 @@ bytes-read=@ud expected-size=(unit @ud) == + +$ state-1 + $: next-id=@ud + connection-by-id=(map @ud [=duct =in-progress-http-request]) + connection-by-duct=(map duct @ud) + outbound-duct=duct + == -- |= old=axle-any ^+ iris-gate ~> %spin.['load/iris'] ?- -.old %~2019.2.8 - %= $ - date.old %~2025.7.17 + %= $ + date.old %~2025.7.17 :: - connection-by-id.state.old - %- ~(run by connection-by-id.state.old) - |= [d=duct r=in-progress-http-request-0] - ^- [duct in-progress-http-request] - :- d - :: set remaining redirects to 0 because we don't have the original request. - :: it's safe to bunt the .request because it only gets used if - :: .remaining-redirects is non-zero. - :: - :- remaining-redirects=0 - +.r(expected-size [expected-size.r *request:http]) + connection-by-id.state.old + %- ~(run by connection-by-id.state.old) + |= [d=duct r=in-progress-http-request-0] + ^- [duct in-progress-http-request] + :- d + :: set remaining redirects to 0 because we don't have the original request. + :: it's safe to bunt the .request because it only gets used if + :: .remaining-redirects is non-zero. + :: + :- remaining-redirects=0 + +.r(expected-size [expected-size.r *request:http]) == + :: %~2025.7.17 + %= $ + date.old %~2026.1.1 + state.old [*(map @ud websocket-connection) state.old] + == + :: + %~2026.1.1 iris-gate(ax old) == :: +stay: produce current state @@ -477,8 +650,9 @@ =* tyl s.bem :: ?. ?=(%& -.why) ~ - =* his p.why - ?: &(?=(%x ren) =(tyl //whey) =([~ ~] lyc)) + ?. =([~ ~] lyc) ~ + :: + ?: &(?=(%x ren) =(tyl //whey)) =/ maz=(list mass) :~ nex+&+next-id.state.ax outbound+&+outbound-duct.state.ax @@ -487,5 +661,44 @@ axle+&+ax == ``mass+!>(maz) - [~ ~] + :: + ~& iris-ws-scry-id=tyl + ?. =([%$ our] why) ~ + ?. &(?=(%x ren) ?=(%$ syd)) ~ + ?+ tyl ~ + [%ws ~] ``noun+!>(sockets.state.ax) + :: + [%ws @ ~] + =/ app=@tas i.t.tyl + :^ ~ ~ %noun + !> ^- (list [wid=@ud url=@t status=?(%accepted %pending)]) + %+ murn ~(tap by sockets.state.ax) + |= [wid=@ud conn=websocket-connection] + ^- (unit [wid=@ud url=@t status=?(%accepted %pending)]) + ?. =(app app.conn) ~ + `[id url status]:conn + :: + [%ws @ %id @ ~] + =/ app=@tas i.t.tyl + =/ wid (slav %ud i.t.t.t.tyl) + ?~ suc=(~(get by sockets.state.ax) wid) + ``noun+!>(~) + ?. =(app.u.suc app) [~ ~] + ``noun+!>(`[id url status]:u.suc) + :: + [%ws @ %url @ ~] + =/ app=@tas i.t.tyl + =/ url=@t i.t.t.t.tyl + =/ sockets ~(tap by sockets.state.ax) + :: pass a (unit websocket-connection) + :^ ~ ~ %noun + !> + |- ^- (unit [wid=@ud url=@t status=?(%accepted %pending)]) + ?~ sockets ~ + =/ socket=websocket-connection q.i.sockets + ?. =(app.socket app) $(sockets t.sockets) + ?. =(url.socket url) $(sockets t.sockets) + `[id url status]:socket + == -- +