diff --git a/.github/workflows/deploy-desk.yml b/.github/workflows/deploy-desk.yml new file mode 100644 index 0000000..d086441 --- /dev/null +++ b/.github/workflows/deploy-desk.yml @@ -0,0 +1,34 @@ +name: Deploy to distribution ship +on: + push: + branches: + - develop + +env: + DESK: nostrill + SHIP_HOST: root@143.198.70.9 + PIER_PATH: /root/.local/share/groundwire-alpha/ribsyp-lidwex-mitdev-sopsyn--difrel-mapler-mitnyt-daplyd + TMUX_TAB: ribsyp + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H 143.198.70.9 >> ~/.ssh/known_hosts + + - name: Deploy desk + run: | + rsync -az --delete --exclude='.git*' \ + app/ ${SHIP_HOST}:${PIER_PATH}/${DESK}/ + + - name: Commit desk on ship + run: | + ssh ${SHIP_HOST} "tmux send-keys -t ${TMUX_TAB} '|commit %${DESK}' Enter" diff --git a/.github/workflows/trigger-urbit-build.yml b/.github/workflows/trigger-urbit-build.yml new file mode 100644 index 0000000..d8aa6cd --- /dev/null +++ b/.github/workflows/trigger-urbit-build.yml @@ -0,0 +1,18 @@ +name: Trigger Groundwire Build +on: + push: + branches: + - develop + - polwex/tlondesks + +jobs: + trigger: + runs-on: ubuntu-latest + steps: + - name: Trigger gwbtc/urbit build + run: | + gh workflow run groundwire-build.yml \ + --repo gwbtc/urbit \ + --ref gw/next/kelvin/408 + env: + GH_TOKEN: ${{ secrets.GH_PAT }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..24d97e2 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# Nostrill + +Poasting on Urbit and Nostr + +## Get started + +The Nostr network uses WebSockets as its only way of communication. Therefore this app required adding WebSockets support to the Urbit runtime and OS. Which we did. + +To run this app you'll need to run an Urbit ship on this experimental runtime, and install an experimental fork of Arvo on it. +There's an easy way and a hard way to do that. + +#### Easy way: + +1. Download vere-ws (for linux x64) [from this link](https://s3.sortug.com/globs/vere-ws.tar.gz). +2. Download the arvo-ws pill [from this link](https://s3.sortug.com/globs/nostrill.pill.tar.gz). +3. Boot a new ship, e.g.: `vere-ws -F zod -B arvo-ws.pill`. + +Your ship should already have Nostrill installed and running. + +#### Hard way: + +1. Clone this repo. +2. Build the runtime +2.1. Go to the `vere` folder and compile the runtime running `zig build`. +2.2. Locate the compiled executable from `vere/zig-out` (the rest of the path depends on your system). We suggest making a new folder in the repo root called `piers` and putting it there. +3. Build the kernel +3.1 Boot a new ship of your liking with the newly compiled runtime. Here let's assume your ship's pier is `piers/zod` in the repo root folder. +3.2. Once the ship is running, mount the base desk by running `|mount %base` on Dojo. +3.3. The Arvo source files with WebSockets support are in the `arvo` folder. Copy or symlink `arvo/lull.hoon` to your pier's `base/sys` folder, and the rest to `base/sys/vane`. +3.4. Recompile the Arvo kernel on Dojo by running `|commit %base`. This process is slow, can take 10-30 minutes +4. Install the app +4.1 Make a new desk in Dojo: `|new-desk %nostrill`. +4.2 Mount it: `|mount %nostrill`. +4.3 Go to your pier and delete the empty desk: `rm -rf piers/zod/nostrill` +4.4 Copy the nostrill desk from the repo root `cp -r app piers/zod/nostrill`. +4.5 Back in Dojo commit the changes: `|commit %nostrill`. +4.6 Install the app `|install %nostrill`. + +Your ship should already have Nostrill installed and running and you should be an hour or so older. And wiser. + +## Features + +### WebSockets support +This repo fullfills the [UIP-0125](https://github.com/urbit/UIPs/blob/main/UIPS/UIP-0125.md) with WebSockets support to both Eyre (server) and Iris (client), and their respective equivalents in Vere. Take a look at the proposal to see which types and data structures are used. + +Below is a short explanation on how to use WebSockets in your typical Gall agent development. In `app/lib/websockets.hoon` there is a simple library which will help with most of the boilerplate. + +#### Use as a server +Say you want to communicate from a frontend to an Urbit agent through WebSockets. Your agent will need to set an Eyre binding to a given path with the `%connect` task. This makes Eyre know where to route the WebSockets request. +Then have the frontend establish a WebSockets connection to that path. Eyre will receive that first connection attempt and send a `%websocket-handshake` poke to your agent. +If your agent accepts the handshake, Eyre will establish the connection, give it an id number, and subscribe to your agent with the path `/websocket-server/[id]`. You can send data to your client by sending `%give %fact` card to that path. +Further messages from that client will be received as pokes with the `%websocket-server-message` mark and vase of shape [id=@ud =path websocket-message:eyre] + +#### Use as a client +To connect from your Gall agent to a WebSockets server, you must pass Iris a `%websocket-connect` task. +If successful, Iris will subscribe to the agent witht he path `/websocket-client/[id]`. +Other than that the flow is very similar. The agent sends WebSockets messages as `%fact` through the open subscription with the vane. +Incoming messages appear as pokes with a `%websocket-client-message` and a vase of shape [id=@ud websocket-message:eyre] + +### Urbit Microblogging + +Nostrill is the spiritual successor to `%trill`. No import mechanism of `%triill` is planned but we will consider if there's demand. +As such you can poast, set a profile, follow people, and build a feed of your favorite content. +Not all the features of `%trill` are present of yet but Nostrill is *much* faster. And it will stay that way. + +### Nostr integration + +Add your favorite Nostr relay in the Settings page (or several), then sync in the Nostr tab at the Home page. Your ship will start receiving messages immediately. + +You can follow users of Nostr, i.e. subscribe to them, and your `%nostrill` will keep receiving every post they publish on one of your set relays. + +As of present only Nostr events kinds `0` (user profiles), `1` (short posts), `6` (reposts) and `7` (reactions) are handled but that will grow soon. + + diff --git a/app/app/nostrill.hoon b/app/app/nostrill.hoon index 9304d99..81f67f1 100644 --- a/app/app/nostrill.hoon +++ b/app/app/nostrill.hoon @@ -15,7 +15,8 @@ feedlib=trill-feed, postlib=trill-post, seed, harklib=hark, - followlib=nostrill-follows + followlib=nostrill-follows, + constants /= web /web/router |% +$ versioned-state $%(state-0:sur) @@ -37,7 +38,7 @@ ^- (quip card:agent:gall agent:gall) =/ default (default-state:lib bowl) :_ this(state default) - bindings:cards + init:cards :: ++ on-save @@ -50,7 +51,7 @@ =/ old-state !<(versioned-state old-state) ?- -.old-state %0 :_ this(state old-state) - bindings:cards + init:cards == :: `this(state (default-state:lib bowl)) @@ -63,12 +64,19 @@ ?+ mark `this %noun handle-comms %json on-ui + %nostrill-ted on-ted %websocket-client-message handle-relay-ws %websocket-handshake handle-ws-handshake %websocket-server-message handle-ws-msg :: %websocket-thread handle-ws-thread :: :: %handle-http-request handle-shim == + ++ on-ted + =/ pok !<(ted:ui vase) + :: =/ poke !<() + =^ cards state (handle-ted:mutan pok) + :_ this cards + +$ ws-msg [@ud websocket-message:eyre] ++ handle-ws-thread ~& >> "proxying ws thread" @@ -94,7 +102,7 @@ ++ handle-ws-handshake ^- (quip card:agent:gall agent:gall) =/ order !<([@ inbound-request:eyre] vase) - ~& >> nostrill-ws-handshake=order + ~& nostrill-ws-handshake=-.order =/ url url.request.order =/ pat=(unit path) (rush url stap) ?~ pat ~& "pat-parsing-failed" `this @@ -112,7 +120,8 @@ =/ msg message.msg.order ?~ msg `this =/ wsdata=@t q.data.u.msg - ~& >> ws-msg-data=[path.order wsdata] + ~& >> websocket-msg-received-from-path=path.order + ~& >> ws-data=wsdata |^ ?+ path.order `this [%nostrill-ui ~] handle-ui-ws @@ -121,7 +130,9 @@ :: ++ handle-ui-ws ^- (quip card:agent:gall agent:gall) - =/ cs (ui-ws-res:lib -.order wsdata) + =/ resmsg (cat 3 wsdata (cat 3 '---' wsdata)) + + =/ cs (ui-ws-res:lib -.order resmsg) [cs this] ++ handle-nostr-client-ws @@ -141,8 +152,11 @@ [cs this] -- ++ handle-comms - =/ pok ;;(poke:comms +.vase) + =/ pok %- (soft poke:comms) +.vase + ?~ pok ~& huh=pok `this + =/ pok u.pok ?: ?=(%dbug -.pok) (debug +.pok) + ?: ?=(%ted -.pok) (ws-proxy +.pok) =^ cs state (handle-eng:mutat +.pok) [cs this] :: @@ -167,7 +181,12 @@ :: =/ nkeys keys(i ks, t `(list keys:nsur)`keys) :: :: =. keys nkeys ~& new-keys=keys - `this + =/ curr (~(get by profiles.state) [%urbit src.bowl]) + =/ np ?~ curr default-profile:scry u.curr(pubkey pub.ks) + =. profiles.state (~(put by profiles.state) [%urbit src.bowl] np) + :_ this + :~ (update-followers:cards:lib [%prof `np]) + == ++ handle-begs |= poke=begs-poke:ui ?- -.poke @@ -190,26 +209,49 @@ ++ handle-prof |= poke=prof-poke:ui ?- -.poke %add - =. profiles (~(put by profiles) [%urbit our.bowl] +.poke) - `this + =/ prof (my-meta-to-prof:scry +.poke) + =. profiles (~(put by profiles) [%urbit our.bowl] prof) + :: send to global relay + =/ nclient ~(. nostr-client [state bowl]) + =/ event (profile-to-event:evlib i.keys.state prof eny.bowl now.bowl) + =/ global-card (send-card:global:nclient [%event event]) + :_ this + :~ (update-followers:cards:lib [%prof `prof]) + global-card + == %del =. profiles (~(del by profiles) [%urbit our.bowl]) - `this + :_ this + :~ (update-followers:cards:lib [%prof ~]) + :: TODO send deletion to relay + == %fetch :: TODO `this == ++ handle-rela |= poke=relay-poke:ui - :: TODO fix this somehow =^ cs state - ?+ -.poke (handle-rela:mutan poke) + ?- -.poke %add :_ state :~ (connect:ws +.poke bowl) == %del (unset-relay:mutan +.poke) + %do (handle-rela:mutan +.poke) == [cs this] :: + ++ ws-proxy |= [wid=@ud w=ws-proxy:comms] + ~& > ws=proxy=[wid w] + ?- -.w + %msg + :_ this + :~ (give-ws-payload-client:ws wid +.w) + == + %disconnect + :_ this + :~ (disconnect:ws wid) + == + == ++ debug |= noun=* ?+ noun `this %hark-c @@ -476,32 +518,79 @@ :_ this :~ (connect:ws endpoint bowl) == + [%wsms @t] + ~& here=+.noun + :_ this + =/ ws-msg=websocket-message:eyre [1 `(as-octs:mimes:html +.noun)] + (give-ws-payload-server-all:ws bowl [%message ws-msg]) + :: + [%wsmc @ @t] + =/ wid +<.noun + =/ cord +>.noun + :_ this + =/ wmsg=websocket-message:eyre [1 `(as-octs:mimes:html cord)] + :~ (give-ws-payload-client:ws wid wmsg) + == + :: + [%wso @t] + :_ this + :~ (connect:ws +.noun bowl) + == + %wsg + :_ this + :~ (connect:ws global-relay:constants bowl) + == + [%wsgp @t] + =/ sp (build-sp:postlib our.bowl our.bowl +.noun ~ ~) + =/ p (build-post:postlib now.bowl pub.i.keys sp) + =/ tag=(list @t) :~('patp' (scot %p our.bowl)) + =/ tags :~(tag) + =/ event (post-to-event:evlib i.keys eny.bowl p 667 tags) + :: =/ p-cord (scot %p our.bowl) + :: =/ str (cat 3 'urbit:' p-cord) + :: =/ tag=(list @t) :~('i' str) + =/ crd + :: (send-and-close:global:nclient [%event event]) + + =/ nclient ~(. nostr-client [state bowl]) + (send-card:global:nclient [%event event]) + + :_ this + :~ crd + == + %wss :: status + ~& global=global-relay-conn + ~& relays=~(tap by relays) + `this %wsl =/ l (list-connected:ws bowl) ~& > ws-connections=l `this - %wsc - =. relays ~ - =. nostr-feed ~ - =/ sockets .^((map @ud websocket-connection:iris) %ix /(scot %p our.bowl)/ws/(scot %da now.bowl)) - ~& iris-sockets=sockets - =/ wids ~(key by sockets) - =/ ws-paths %+ turn ~(tap in wids) |= wid=@ ^- path /websocket-client/(scot %ud wid) - ~& ws-paths=ws-paths + %wsc :: close + ~& > ~(val by relays) + =. relays *(map @ud relay-stats:nsur) + =. global-relay-conn ~ :_ this - ?~ ws-paths ~ - :~ [%give %fact ws-paths %disconnect !>(~)] - == - %ws-close + ~& >>> ~(val by relays) + =/ l (list-connected:ws bowl) + %+ turn l |= [wid=@ url=@t status=*] (cancel-connect:ws wid) + :: + [%ws-ui @] :: status :_ this - =/ inc-subs ~(tap by sup.bowl) - =/ ws-paths %+ roll inc-subs |= [i=[=duct =ship =path] acc=(list path)] - ?. ?=([%websocket-client *] path.i) acc - ~& bitt=i - [path.i acc] - ?~ ws-paths ~ - :~ [%give %fact ws-paths %disconnect !>(~)] + =/ pats (list-server-conns:ws bowl) + ?~ pats ~& >>> "no connections to this ws server" ~ + =/ pol=(pole knot) (tail i.pats) + ?. ?=([wids=@ ~] pol) ~& >>> "weird" ~ + =/ wid (slav %ud wids.pol) + ~& wid=wid + (ui-ws-res:lib wid +.noun) + %wst :: status + :_ this + ~& "testing ws thread" + =/ wmsg (string-message:ws '試行中') + :~ (one-off:ws 'ws://localhost:9000' wmsg bowl) == + :: %irisf :_ this =/ inc-subs ~(tap by sup.bowl) @@ -523,6 +612,13 @@ =/ res (check-connected:ws 'ws://localhost:8888' bowl) ~& res `this + %fols + ~& following=~(key by following) + ~& followers=get-followers:scry + `this + [%fol @p] + ~& (~(get by following) [%urbit +.noun]) + `this %nostr =/ rls ~(tap by relays) =/ m |- ?~ rls ~ @@ -532,27 +628,31 @@ =/ reqs ~(tap by reqs.stats) =/ mm |- ?~ reqs ~ =/ sub -.i.reqs - ~& event-stats=[sub-id=sub +.i.reqs] + ~& req=[sub-id=sub +.i.reqs] $(reqs t.reqs) $(rls t.rls) - ~& > "nostr feed" + ~& > "nostr feed" `this %nf =/ nf (tap:norm:sur nostr-feed) =/ nff |- ?~ nf ~ - =/ ev=event:nsur +.i.nf + =/ ev=wevent:nsur +.i.nf ~& meta=[kind=kind.ev id=id.ev pubkey=pubkey.ev ts=created-at.ev] ~& >> ev-txt=content.ev $(nf t.nf) + `this + %prof + =/ prof (~(get by profiles) [%urbit our.bowl]) + ~& > own-prof=prof `this %profs =/ pfs ~(tap by profiles) ~& stored-profiles=(lent pfs) =/ nff |- ?~ pfs ~ =/ u=user:sur -.i.pfs - =/ prof=user-meta:nsur +.i.pfs + =/ prof=user-profile:comms +.i.pfs ~& >> user=u ~& > profile=prof $(pfs t.pfs) @@ -604,37 +704,37 @@ :~ (urbit-watch:fols ~zod) [%pass /foldbug %agent [~zod dap.bowl] %poke %bitch !>(~)] == - :: requires a relay - :: - %rt0 - =/ rl get-relay:mutan - ?~ rl ~& >>> "no relay!!!!" `this - =/ wid -.u.rl - =/ relay +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) - =^ cards relay get-profiles:nclient - =. relays (~(put by relays) wid relay) - [cards this] - %wstest - :: =/ url 'ws://localhost:8888' - :: =/ url 'wss://nos.lol' - =/ rl get-relay:mutan - ?~ rl ~& >>> "no relay!!!!" `this - =/ wid -.u.rl - =/ relay +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) - =^ cs relay test-connection:nclient - =. relays (~(put by relays) wid relay) - [cs this] - %rt :: relay test - =/ rl get-relay:mutan - ?~ rl ~& >>> "no relay!!!!" `this - =/ wid -.u.rl - =/ relay +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) - =^ cards relay get-posts:nclient - =. relays (~(put by relays) wid relay) - [cards this] + :: :: TODO refactoring into mutations + :: :: + :: %rt0 + :: =/ rl get-relay:mutan + :: ?~ rl ~& >>> "no relay!!!!" `this + :: =/ wid -.u.rl + :: =/ relay +.u.rl + :: =/ nclient ~(. nostr-client [state bowl]) + :: =/ rclient ~(. relay.nclient [wid relay]) + :: =^ cards state get-profiles:rclient + :: [cards this] + :: %wstest + :: :: =/ url 'ws://localhost:8888' + :: :: =/ url 'wss://nos.lol' + :: =/ rl get-relay:mutan + :: ?~ rl ~& >>> "no relay!!!!" `this + :: =/ wid -.u.rl + :: =/ relay +.u.rl + :: =/ nclient ~(. nostr-client [state bowl]) + :: =/ rclient ~(. relay.nclient [wid relay]) + :: =^ cs state test-connection:sclient + :: [cs this] + :: %rt :: relay test + :: =/ rl get-relay:mutan + :: ?~ rl ~& >>> "no relay!!!!" `this + :: =/ wid -.u.rl + :: =/ relay +.u.rl + :: =/ nclient ~(. nostr-client [state bowl]) + :: =/ rclient ~(. relay.nclient [wid relay]) + :: =^ cards state get-posts:rclient + :: [cards this] == :: -- @@ -676,8 +776,26 @@ |~ =(pole knot) ^- (quip card:agent:gall agent:gall) ~& >>> on-leave=pole - :: TODO fix the relays when we doing this - `this + ?+ pole `this + [%websocket-client wids=@ ~] + :: reconnect logic requires knowing which side dropped the connection, which is... tricky + `this + :: ~& websocket-client-connection-dropped=`@t`wids.pole + :: =/ wid (slav %ud wids.pole) + :: :: check if it's the global relay + :: =/ is-global .= `wid global-relay-conn + :: ?: is-global + :: ~& reconnecting-to-global=wid + :: :_ this :~((connect:ws global-relay:constants bowl)) + :: :: + :: =/ relay (~(get by relays) wid) + :: ?~ relay `this :: that would be weird + :: :_ this + :: ~& reconnecting=url.u.relay + :: :~ (connect:ws url.u.relay bowl) + :: == + + == :: ++ on-peek |~ =(pole knot) @@ -705,9 +823,13 @@ [cs this] ?. ?=(%fact -.sign) `this ~& "got fact" - ~& fact=(@t -.q.q.cage.sign) + ~& fact=((soft @t) -.q.q.cage.sign) - =/ =fact:comms ;; fact:comms q.q.cage.sign + =/ ufact %- (soft fact:comms) q.q.cage.sign + + ~& >> clammed-fact=ufact + ?~ ufact `this + =/ =fact:comms u.ufact =^ cs state ?- -.fact %feed (handle-res:fols +.fact) @@ -726,6 +848,14 @@ :: ~& > +.sign-arvo `this ?+ wire `this + [%ws-oneoff *] + ?> ?=([%khan %arow *] sign-arvo) + ~& >>> -.p.sign-arvo + ?: ?=(%| -.p.sign-arvo) `this + =/ =cage +.p.sign-arvo + =/ v=vase q.cage + ~& cage=cage + `this [%ws %to-nostr-relay *] ?> ?=([%khan %arow *] sign-arvo) ?: ?=(%| -.p.sign-arvo) `this @@ -742,7 +872,7 @@ =/ jstring=@t q.octs ~& >> jstring=jstring - :: =/ msg (parse-body:nclient jstring) + :: =/ msg (parse-body:nostr-client jstring) :: ~& "m5" :: ?~ msg ~& badparse=`@t`jstring `this :: ~& >> ws-relay-msg=msg @@ -753,7 +883,7 @@ :: ?> ?=([%khan %arow *] sign-arvo) :: ?: ?=(%| -.p.sign-arvo) `this :: =/ jstring !<(@ +>.p.sign-arvo) - :: =/ msg (parse-body:nclient jstring) + :: =/ msg (parse-body:nostr-client jstring) :: ?~ msg ~& `@t`jstring `this :: ~& >> ws-ui-msg=msg :: :: ?> ?=(%http -.u.msg) diff --git a/app/desk.docket-0 b/app/desk.docket-0 index e30a841..e2e0735 100644 --- a/app/desk.docket-0 +++ b/app/desk.docket-0 @@ -1,11 +1,11 @@ :~ - title+'Nostrill' + title+'Trill 2' info+'Pater Nostr' color+0xff.d400 - version+[0 1 0] - website+'https://sortug.com' - license+'© 2025 ~sortug. All rights reserved.' - image+'https://s3.sortug.com/img/nostril-icon.png' - base+'nostril' - glob-http+['https://s3.sortug.com/globs/glob-0v1.862v7.o85ao.krj6r.v1dgc.ek27l.glob' 0v1.862v7.o85ao.krj6r.v1dgc.ek27l] -== \ No newline at end of file + version+[0 1 1] + website+'https://groundwire.dev' + license+'Noli me tangere' + image+'https://s3.sortug.com/img/nostrill-tile.png' + base+'nostrill' + glob-http+['https://s3.sortug.com/globs/glob-0v2.0n7m8.g69ms.uejn8.dkget.1jm7v.glob' 0v2.0n7m8.g69ms.uejn8.dkget.1jm7v] +== diff --git a/app/lib/constants.hoon b/app/lib/constants.hoon index 9beab32..907814a 100644 --- a/app/lib/constants.hoon +++ b/app/lib/constants.hoon @@ -1,4 +1,6 @@ |% ++ feed-page-size 100 ++ http-delay 3.000 +++ global-relay 'wss://nostr.sortug.com' +:: ++ global-relay 'ws://192.168.1.110:8080' -- diff --git a/app/lib/json/common.hoon b/app/lib/json/common.hoon index be076c2..6985768 100644 --- a/app/lib/json/common.hoon +++ b/app/lib/json/common.hoon @@ -27,6 +27,13 @@ ++ se |= aur=@tas |= jon=json ?. ?=(%s -.jon) ~ (slaw aur p.jon) + ++ string-ud + |= jon=json ^- (unit @) + ?. ?=([%s @t] jon) ~ + (rush p.jon dem) + + :: ++ un |* wit=fist |= jon=json ^- (unit *) + :: ?~ jon ~ -- -- diff --git a/app/lib/json/nostr.hoon b/app/lib/json/nostr.hoon index 05713a5..9c90c9d 100644 --- a/app/lib/json/nostr.hoon +++ b/app/lib/json/nostr.hoon @@ -37,6 +37,13 @@ a+(turn tags tag) s+content == + ++ wevent + |= w=wevent:sur ^- json + %: pairs + relays+a+(turn relays.w cord:en:common) + event+(event +.w) + ~ + == ++ event =/ nostr=? .n |= e=event:sur ^- json @@ -84,13 +91,18 @@ ++ user-meta |= meta=user-meta:sur - %: pairs - name+s+name.meta - picture+s+picture.meta + %- pairs (user-meta-pairs meta) + + ++ user-meta-pairs + |= meta=user-meta:sur ^- (list [@t json]) + %+ weld ^- (list [@t json]) + :~ name+s+name.meta about+s+about.meta - other+o+other.meta - ~ + picture+s+picture.meta + :- 'patp' ?~ patp.meta ~ (patp:en:common u.patp.meta) == + %+ turn ~(tap by other.meta) |= a=[@t json] a + ++ relay-msg |= msg=relay-msg:sur ^- json =/ head [%s -.msg] :- %a :- head @@ -282,6 +294,9 @@ %'image' =/ crd (so jn) ?~ crd $(fields t.fields) $(fields t.fields, um um(picture u.crd)) + %'patp' + =/ crd ((se:de:common %p) jn) + $(fields t.fields, um um(patp crd)) == -- -- diff --git a/app/lib/json/nostrill.hoon b/app/lib/json/nostrill.hoon index 7f74119..e81dbc9 100644 --- a/app/lib/json/nostrill.hoon +++ b/app/lib/json/nostrill.hoon @@ -40,8 +40,8 @@ == ++ en-nostr-feed |= feed=nostr-feed:sur ^- json - :- %a %+ turn (tap:norm:sur feed) |= [id=@ud ev=event:nsur] - (event:en:nostr ev) + :- %a %+ turn (tap:norm:sur feed) |= [id=@ud wev=wevent:nsur] + (wevent:en:nostr wev) ++ en-relays |= r=(map @ relay-stats:nsur) ^- json @@ -52,23 +52,40 @@ :- %wid (numb wid) :- %reqs (relay-stats reqs.rs) == - ++ relay-stats |= rm=(map @t event-stats:nsur) - %- pairs %+ turn ~(tap by rm) |= [sub-id=@t es=event-stats:nsur] - :: TODO do we even need this - :- sub-id (numb received.es) + ++ relay-stats |= rm=(map @t req-state:nsur) + %- pairs %+ turn ~(tap by rm) |= [sub-id=@t rq=req-state:nsur] + :- sub-id + %- pairs + :~ :+ 'name' %s name.rq + :+ 'filters' %a (turn filters.rq filter:en:nostr) + :: :: TODO chunks... + :- 'eventsReceived' (numb received.rq) + :- 'ongoing' ?~ ongoing.rq ~ [%b u.ongoing.rq] + == - ++ en-profiles |= m=(map user:sur user-meta:nsur) + ++ en-profiles |= m=(map user:sur user-profile:comms) %- pairs - %+ turn ~(tap by m) |= [key=user:sur p=user-meta:nsur] - =/ jkey (user key) + %+ turn ~(tap by m) |= [key=user:sur p=user-profile:comms] + =/ jkey (user-string key) ?> ?=(%s -.jkey) - :- +.jkey (user-meta:en:nostr p) + :- +.jkey (en-profile p) + + ++ en-profile |= prof=user-profile:comms + %- pairs %+ weld + :~ + :- 'pubkey' (hex:en:common pubkey.prof) + :+ 'following' %a %+ turn ~(tap in following.prof) user + :- 'followingCount' (numb following-count.prof) + :+ 'followers' %a %+ turn ~(tap in followers.prof) user + :- 'followerCount' (numb follower-count.prof) + == + (user-meta-pairs:en:nostr +>+>+:prof) ++ enfollowing |= m=(map user:sur feed:tf) ^- json %- pairs %+ turn ~(tap by m) |= [key=user:sur f=feed:tf] - =/ jkey (user key) + =/ jkey (user-string key) ?> ?=(%s -.jkey) :: TODO proper cursor stuff :- +.jkey (feed-with-cursor:en:trill f ~ ~) @@ -77,7 +94,7 @@ |= m=(map user:sur (set user:sur)) ^- json %- pairs %+ turn ~(tap by m) |= [key=user:sur s=(set user:sur)] - =/ jkey (user key) + =/ jkey (user-string key) ?> ?=(%s -.jkey) :- +.jkey :- %a %+ turn ~(tap in s) user @@ -90,6 +107,12 @@ :- %relay ?~ relay.f ~ s+u.relay.f == ++ user |= u=user:sur ^- json + %+ frond -.u + ?- -.u + %urbit (patp:en:common +.u) + %nostr (hex:en:common +.u) + == + ++ user-string |= u=user:sur ^- json ?- -.u %urbit (patp:en:common +.u) %nostr (hex:en:common +.u) @@ -108,8 +131,9 @@ ++ folsfact |= f=fols-fact:ui ^- json %+ frond -.f ?- -.f - %new (fols +.f) - %quit (user +.f) + %new-urbit (fols +.f) + %new-nostr (en-followed +.f) + %quit (user-string +.f) == ++ en-nostr |= nf=nostr-fact:ui ^- json %+ frond -.nf @@ -118,7 +142,24 @@ %user (en-nostr-feed +.nf) %thread (en-nostr-feed +.nf) %event (event:en:nostr +.nf) + %eose (cord:en:common +.nf) %relays (en-relays +.nf) + :: + %sent-post (en-nostr-sent-post +.nf) + %sent-prof [%a (turn relays.nf cord:en:common)] + == + ++ en-followed |= [pubkey=@ux profile=(unit user-profile:comms) relays=(list @t)] + %- pairs :~ + pubkey+(hex:en:common pubkey) + :- %profile ?~ profile ~ (en-profile u.profile) + relays+a+(turn relays cord:en:common) + == + ++ en-nostr-sent-post |= [host=@p id=@ urls=(list @t) ev=event:nsur] ^- json + %- pairs :~ + host+s+(scot %p host) + id+(ud:en:common id) + relays+a+(turn urls cord:en:common) + event+(event:en:nostr ev) == ++ user-data |= ud=[=fc:tf profile=(unit user-meta:nsur)] @@ -169,7 +210,7 @@ |= fd=feed-data:comms %: pairs feed+(feed-with-cursor:en:trill fc.fd) - :- %profile ?~ profile.fd ~ (user-meta:en:nostr u.profile.fd) + profile+(en-profile profile.fd) ~ == @@ -181,7 +222,7 @@ ++ nostr-meta |= p=nostr-meta:comms ^- json %- pairs :~ ['pubkey' (hex:en:common pub.p)] - :- 'profile' ?~ prof.p ~ (user-meta:en:nostr u.prof.p) + :- 'profile' ?~ prof.p ~ (en-profile u.prof.p) :- 'eventId' ?~ ev-id.p ~ (hex:en:common u.ev-id.p) :+ 'relay' %a %+ turn relays.p cord:en:common == @@ -214,12 +255,7 @@ ++ ui-begs %- of :~ feed+(se:de:common %p) - thread+de-pid - == -++ de-pid - %- ot :~ - host+(se:de:common %p) - id+de-atom-id + thread+pid:de:trill == ++ ui-prof %- of :~ @@ -232,8 +268,14 @@ name+so about+so picture+so + patp+de-unit-patp other+other-meta == +++ de-unit-patp |= jon=json ^- (unit (unit @p)) + ?. ?=(%s -.jon) ~ + %- some ((se:de:common %p) jon) + :: we have this for type economy but a user does not get to change their profile's @p in the UI + ++ other-meta |= jon=json ?. ?=(%o -.jon) ~ (some p.jon) ++ ui-post @@ -241,14 +283,16 @@ add+postadd reply+reply quote+quote - rp+pid + rp+upid :: rt+de-rt reaction+reaction - del+pid + del+upid == ++ postadd %- ot :~ content+so + global+bo + anon+bo == ++ reply %- ot :~ @@ -263,10 +307,10 @@ host+user id+de-post-id == -++ pid +++ upid %- ot :~ host+user - id+de-atom-id + id+string-ud:de:common == ++ reaction %- ot :~ @@ -284,18 +328,23 @@ %- of :~ add+so del+ni - sync+ul - prof+ul - user+hex:de:common - thread+hex:de:common - send+de-relay-send + do+de-relay-do == -++ de-relay-send %- ot :~ - host+(se:de:common %p) - id+de-atom-id - relays+(ar so) +++ de-relay-do + %- ot + :~ relays+(ar ni) + action+de-relay-action == + ++ de-relay-action + %- of :~ + sync+ul + prof+ul + user+hex:de:common + thread+hex:de:common + send-post+pid:de:trill + send-prof+ul + == ++ de-post-id |= jon=json ^- (unit @) ?. ?=([%s @t] jon) ~ @@ -303,11 +352,6 @@ ?^ tryatom tryatom ^- (unit @) (hex:de:common jon) -++ de-atom-id - |= jon=json ^- (unit @) - ?. ?=([%s @t] jon) ~ - (rush p.jon dem) - -- -- diff --git a/app/lib/json/trill.hoon b/app/lib/json/trill.hoon index 07eb796..8becec0 100644 --- a/app/lib/json/trill.hoon +++ b/app/lib/json/trill.hoon @@ -271,6 +271,11 @@ ++ de =, dejs-soft:format |% + ++ pid + %- ot + :~ host+(se:de:common %p) + id+string-ud:de:common + == ++ perms %- ot :~ read+gate diff --git a/app/lib/mutations/nostr.hoon b/app/lib/mutations/nostr.hoon index a017f45..4cca0a7 100644 --- a/app/lib/mutations/nostr.hoon +++ b/app/lib/mutations/nostr.hoon @@ -12,27 +12,43 @@ nostr-client, sr=sortug, scri, + constants, ws=websockets |_ [=state:sur =bowl:gall] +* cardslib ~(. cards:lib bowl) +$ card card:agent:gall + +++ empty-nostr-profile |= [pubkey=@ux meta=user-meta:nsur] ^- user-profile:comms + :- pubkey + :- ~ + :- 0 + :- ~ + :- 0 + meta + :: relay state ++ get-relay ^- (unit [wid=@ud relay=relay-stats:nsur]) =/ rls ~(tap by relays.state) ?~ rls ~ `i.rls - + ++ set-relay |= wid=@ud ^- (quip card _state) =/ socket (get-url:ws wid bowl) ?~ socket ~& "socket wid not in iris" !! ?. ?=(%accepted status.u.socket) ~& "socket status in iris unsync" !! - =/ relay=relay-stats:nsur [now.bowl url.u.socket ~] - =. relays.state (~(put by relays.state) wid relay) - :_ state - =/ ui-card (update-ui:cardslib [%nostr %relays relays.state]) - :~(ui-card) + :: Don't add to relays state if it's the global-feed relay + ?: .= url.u.socket global-relay:constants + =. global-relay-conn.state `wid + :: TODO ui card? + `state + :: + =/ relay=relay-stats:nsur [now.bowl url.u.socket ~] + =. relays.state (~(put by relays.state) wid relay) + :_ state + =/ ui-card (update-ui:cardslib [%nostr %relays relays.state]) + :~(ui-card) ++ unset-relay |= wid=@ud ^- (quip card _state) @@ -47,9 +63,10 @@ :: events ++ handle-client-event |= [wid=@ =event:nsur] ^- (quip card _state) ~& handling-client-event=event - =. nostr-feed.state (put:norm:sur nostr-feed.state created-at.event event) - =/ profile (~(get by profiles.state) [%nostr pubkey.event]) - :: TODO save if we're following? + :: =/ wevent :- :~('our') event + :: =. nostr-feed.state (put:norm:sur nostr-feed.state created-at.event wevent) + :: =/ profile (~(get by profiles.state) [%nostr pubkey.event]) + :: :: TODO save if we're following? :: =/ pw (event-to-post:nlib event profile) =/ response (ok-client-event:nreq event .n 'we\'re full') =/ cs (ws-response:nreq wid response) @@ -80,7 +97,8 @@ :: [cards state] ++ handle-ws |= [wid=@ud relay=relay-stats:nsur msg=relay-msg:nsur] - =/ nclient ~(. nostr-client [state bowl wid relay]) + =/ nclient ~(. nostr-client [state bowl]) + =/ rclient ~(. relay.nclient [wid relay]) |^ =^ cards state ~& > handle-ws=-.msg @@ -154,6 +172,8 @@ parse-follow ?: .=(kind.event 7) :: Reaction parse-follow + :: ?: .=(kind.event 667) :: Reaction + :: parse-global `state [(weld cs1 cs) state] @@ -164,16 +184,22 @@ ?~ ujon ~& failed-parse-metadata=ujon `state =/ umeta (user-meta:de:njs u.ujon) ?~ umeta ~& >> failed-dejs-metadata=ujon `state - =. profiles.state (~(put by profiles.state) [%nostr pubkey.event] u.umeta) - `state - + =/ prof (empty-nostr-profile pubkey.event u.umeta) + =. profiles.state (~(put by profiles.state) [%nostr pubkey.event] prof) + :_ state + ~ + ++ parse-poast ^- (quip card _state) - =. nostr-feed.state (put:norm:sur nostr-feed.state created-at.event event) - =/ user-feed (~(get by following.state) [%nostr pubkey.event]) + + =/ wevent :- :~(url.relay) event + =. nostr-feed.state (put:norm:sur nostr-feed.state created-at.event wevent) + =/ user [%nostr pubkey.event] + =/ user-feed (~(get by following.state) user) + =/ profile (~(get by profiles.state) user) =? following.state ?=(^ user-feed) - =/ pw (event-to-post:evlib event ~ ~) + =/ pw (event-to-post:evlib event profile `url.relay) =/ poast=post:post -.pw =/ nf (put:orm:feed u.user-feed id.poast poast) (~(put by following.state) [%nostr pubkey.event] nf) @@ -236,98 +262,230 @@ ~& >>> "HANDLING-EOSE-FROM-SERVER" ~& sub-id :: TODO better UI facts - =/ creq (~(get by reqs.relay) sub-id) - ?~ creq ~& >>> "sub id not found! on eose" `state - ~& >> eose=u.creq - ~& >>> "**************" + =/ ureq=(unit req-state:nsur) (~(get by reqs.relay) sub-id) + ?~ ureq ~& >>> "sub id not found! on eose" `state + =/ reqs=req-state:nsur u.ureq + ~& >> eose=reqs + ~& >>> "**************" :: :: if there's a queue we setup the next subscription =^ cards relay - ?: (is-feed:evlib filters.u.creq) + ?: (is-feed:evlib filters.reqs) ~& >> "eose on global feed request" =/ c (update-ui:cardslib [%nostr %feed nostr-feed.state]) - =^ mc relay get-profiles:nclient + =^ mc relay get-profiles:rclient [[c mc] relay] :: - =/ users=(set @ux) (user-req:evlib filters.u.creq) + =/ users=(set @ux) (user-req:evlib filters.reqs) ?: (gth ~(wyt in users) 0) ~& >>> "eose on user feed request" =/ poasts (tap:norm:sur nostr-feed.state) - =/ subset %+ skim poasts |= [* ev=event:nsur] (~(has in users) pubkey.ev) + =/ subset %+ skim poasts |= [* ev=wevent:nsur] (~(has in users) pubkey.ev) =/ f (gas:norm:sur *nostr-feed:sur subset) =/ c (update-ui:cardslib [%nostr %user f]) [:~(c) relay] - =/ thread-id (thread-req:evlib filters.u.creq) + =/ thread-id (thread-req:evlib filters.reqs) ?^ thread-id ~& >>> "eose on thread request" =/ poasts (tap:norm:sur nostr-feed.state) - =/ subset %+ skim poasts |= [* ev=event:nsur] + =/ subset %+ skim poasts |= [* ev=wevent:nsur] ?| .=(u.thread-id id.ev) - =/ refs (get-references:evlib ev) + =/ refs (get-references:evlib +.ev) (~(has in refs) u.thread-id) == =/ f (gas:norm:sur *nostr-feed:sur subset) =/ c (update-ui:cardslib [%nostr %thread f]) [:~(c) relay] :: - ?: (profs-req:evlib filters.u.creq) + ?: (profs-req:evlib filters.reqs) =/ c (update-ui:cardslib [%prof profiles.state]) [:~(c) relay] :: [~ relay] - :: + :: if chunked request we move the queue and send the new request =^ cards2 relay - ?~ chunked.u.creq [~ relay] - =/ head i.chunked.u.creq - =/ tail t.chunked.u.creq - =/ ncreq=event-stats:nsur [filters.u.creq received.u.creq ongoing.u.creq ~] - =. reqs.relay (~(put by reqs.relay) sub-id ncreq) - (send-req:nclient :~(head) ongoing.u.creq tail) - :: + ?~ chunked.reqs [~ relay] + =/ head i.chunked.reqs + =/ tail t.chunked.reqs + =/ d (set-req:nclient relay name.reqs :~(head) ongoing.reqs tail) + :_ +.d + :~ (send-card:rclient -.d) + == + :: if ongoing request we mark it as backlog received and keep it alive, else we cloe it =^ cards3 relay - ?~ ongoing.u.creq - ~& >>> closing-relay-sub=[sub-id filters.u.creq] - (close-sub:nclient sub-id wid relay) - =/ ncreq=event-stats:nsur [filters.u.creq received.u.creq `.y ~] - =. reqs.relay (~(put by reqs.relay) sub-id ncreq) + ?~ ongoing.reqs + ~& >>> closing-relay-sub=[sub-id filters.reqs] + =/ d (close-sub-req:nclient sub-id wid relay) + :_ +.d + :~ (send-card:rclient -.d) + == + =. ongoing.reqs `.y + =. reqs.relay (~(put by reqs.relay) sub-id reqs) [~ relay] - :: + + =/ eose-card (update-ui:cardslib [%nostr %eose sub-id]) + =/ carrds :- eose-card %+ weld cards %+ weld cards2 cards3 + =. relays.state (~(put by relays.state) wid relay) - :_ state (weld (weld cards cards2) cards3) + :_ state carrds -- - ++ handle-prof-fact |= pf=prof-fact:comms + ++ handle-prof-fact |= prof=(unit user-profile:comms) + ^- (quip card _state) + =. profiles.state ?~ prof + (~(del by profiles.state) [%urbit src.bowl]) + (~(put by profiles.state) [%urbit src.bowl] u.prof) + :: TODO kinda wanna send it to the UI + `state + + + ++ call-relay |= [wid=@ud relay-url=@t ro=relay-order:ui] ^- (quip card _state) - =/ =user:sur [%urbit src.bowl] - ?- -.pf - %prof =. profiles.state (~(put by profiles.state) user +.pf) - :: TODO kinda wanna send it to the UI - `state - %keys `state - :: TODO really need a way to keep track of everyone's pubkeys + =/ nclient ~(. nostr-client [state bowl]) + =/ urelay (~(get by relays.state) wid) + ?~ urelay + ~& >>> not-connected-to-relay=wid + (test-reconnection relay-url ro) + :: + =/ relay u.urelay + =/ rclient ~(. relay.nclient [wid relay]) + :: + =/ d=[(list card) _relay] + ?+ -.ro !! + %user (get-user-feed:rclient +.ro) + %thread (get-thread:rclient +.ro) + %sync get-posts:rclient + %prof get-profiles:rclient == + =. relays.state (~(put by relays.state) wid +.d) + [-.d state] + + ++ test-reconnection |= [relay-url=@t ro=relay-order:ui] + ^- (quip card _state) + =/ d (wait-for-connection:ws relay-url bowl) + `state + + + + ++ handle-ted |= r=ted:ui + ^- (quip card _state) + ?. ?=(%req -.r) `state + (relay-get +.r) + + ++ relay-get |= [tid=@ta wids=(list @ud) rg=relay-get:ui] + ^- (quip card _state) + ~& >> got-tid=tid + =/ nclient ~(. nostr-client [state bowl]) + =^ cards state + =| css=(list card) + |- ?~ wids [css state] + =/ wid=@ud i.wids + =/ urelay (~(get by relays.state) wid) + ?~ urelay + ~& >>> not-connected-to-relay=wid + $(wids t.wids) + =/ relay u.urelay + =/ rclient ~(. relay.nclient [wid relay]) + :: + + =/ d + ?+ -.rg !! + %sync get-posts-ted:rclient + == + =. reqs.relay (~(put by reqs.relay) sub-id.d rs.d) + =. relays.state (~(put by relays.state) wid relay) + =/ cs=(list card) + :~ (poke-ui-thread:cards:lib tid sub-id.d) + card.d + == + =/ ncss (weld cs css) + $(wids t.wids, css ncss) + [cards state] + + :: Handle pokes from UI related to relay interaction ++ handle-rela |= rh=relay-handling:ui ^- (quip card _state) - =/ rl get-relay - ?~ rl ~& >>> "no relay!!!!" `state - =/ wid=@ud -.u.rl - =/ relay=relay-stats:nsur +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) - ?: ?=(%send -.rh) + ~& handle-rela-mutan=rh + =/ nclient ~(. nostr-client [state bowl]) + =/ wids relays.rh + |^ + ?: ?=(%send-prof -.action.rh) :_ state (send-prof relays.rh) + ?: ?=(%send-post -.action.rh) :_ state (send-post relays.rh +.action.rh) + =^ cards state + + =| css=(list card) + |- ?~ wids [css state] + =/ wid=@ud i.wids + =/ urelay (~(get by relays.state) wid) + ?~ urelay + ~& >>> not-connected-to-relay=wid + $(wids t.wids) + =/ relay u.urelay + =/ rclient ~(. relay.nclient [wid relay]) + :: + =/ d=[(list card) _relay] + ?- -.action.rh + %user (get-user-feed:rclient +.action.rh) + %thread (get-thread:rclient +.action.rh) + %sync get-posts:rclient + %prof get-profiles:rclient + == + =. relays.state (~(put by relays.state) wid +.d) + $(wids t.wids, css (weld css -.d)) + [cards state] + + + ++ send-post |= [wids=(list @ud) host=@p id=@da] + ^- (list card) =/ scry ~(. scri [state bowl]) - =/ upoast (get-poast:scry host.rh id.rh) - ?~ upoast `state - =/ event (post-to-event:evlib i.keys.state eny.bowl u.upoast) - =/ cs :~((send:nclient url.relay [%event event])) - [cs state] - =^ cs relay - ?- -.rh - %sync get-posts:nclient - %user (get-user-feed:nclient +.rh) - %thread (get-thread:nclient +.rh) - %prof get-profiles:nclient - :: - == - =. relays.state (~(put by relays.state) -.u.rl relay) - [cs state] + =/ upoast (get-poast:scry host id) + ?~ upoast ~& >>> post-to-relay-not-found=[host id] ~ + :: TODO + =/ tags=(list (list @t)) ~ + =/ event (post-to-event:evlib i.keys.state eny.bowl u.upoast 1 tags) + ~& >>> sending=id + =| urls=(list @t) + =/ cards=(list card) + =| cs=(list card) + |- ?~ wids cs + =/ wid=@ud i.wids + =/ urelay (~(get by relays.state) wid) + ?~ urelay + ~& >>> not-connected-to-relay=wid + $(wids t.wids) + =/ relay u.urelay + + =/ rclient ~(. relay.nclient [wid relay]) + =/ ncs :_ cs (send-card:rclient [%event event]) + =/ nurls :_ urls url.relay + $(wids t.wids, cs ncs, urls nurls) + :- (update-ui:cardslib [%nostr %sent-post host id urls event]) + cards + + + ++ send-prof + |= wids=(list @ud) + ^- (list card) + =/ prof (~(get by profiles.state) [%urbit src.bowl]) + ?~ prof ~& "send-prof failed" ~ + =/ event (profile-to-event:evlib i.keys.state u.prof eny.bowl now.bowl) + =| urls=(list @t) + =/ cards=(list card) + =| cs=(list card) + |- ?~ wids cs + =/ wid=@ud i.wids + =/ urelay (~(get by relays.state) wid) + ?~ urelay + ~& >>> not-connected-to-relay=wid + $(wids t.wids) + =/ relay u.urelay + + =/ rclient ~(. relay.nclient [wid relay]) + =/ ncs :_ cs (send-card:rclient [%event event]) + =/ nurls :_ urls url.relay + $(wids t.wids, cs ncs, urls nurls) + :- (update-ui:cardslib [%nostr %sent-prof urls]) + :- (send-card:global:nclient [%event event]) + cards + -- -- diff --git a/app/lib/mutations/reqs.hoon b/app/lib/mutations/reqs.hoon index 3c84571..2d0c358 100644 --- a/app/lib/mutations/reqs.hoon +++ b/app/lib/mutations/reqs.hoon @@ -1,5 +1,5 @@ /- *wrap, sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed, post=trill-post, notif=nostrill-notif -/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill, lib=nostrill, mutations-trill, harklib=hark +/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill, lib=nostrill, mutations-trill, harklib=hark, scri |_ [=state:sur =bowl:gall] ++ handle-req |= [=req:comms pat=path] @@ -70,7 +70,9 @@ =/ lp latest-page:feedlib =/ lp2 lp(count backlog.feed-perms.state) =/ =fc:feed (lp2 feed.state) - =/ prof (~(get by profiles.state) [%urbit our.bowl]) + =/ uprof (~(get by profiles.state) [%urbit our.bowl]) + =/ prof ?^ uprof u.uprof + ~(default-profile scri [state bowl]) =/ fd=feed-data:comms [fc prof] =/ fr=fols-res:comms [msg.decision %done %ok fd] :_ state diff --git a/app/lib/mutations/trill.hoon b/app/lib/mutations/trill.hoon index 0650382..f422b55 100644 --- a/app/lib/mutations/trill.hoon +++ b/app/lib/mutations/trill.hoon @@ -9,6 +9,7 @@ njs=json-nostr, postlib=trill-post, sr=sortug, + constants, :: mutations-nostr, nostr-client, @@ -185,15 +186,37 @@ =/ mentions (extract-mentions:postlib p) =/ mention-cards %+ turn mentions |= s=@p %+ poke-host:crds s [%eng %mention p] + =/ global-cards=(list card:agent:gall) ?. global.poke ~ + + =/ nclient ~(. nostr-client [state bowl]) + =/ tag=(list @t) :~('patp' (scot %p our.bowl)) + =/ tags :~(tag) + =/ event (post-to-event:evlib i.keys.state eny.bowl p 667 tags) + =/ mutan ~(. mutations-nostr [state bowl]) + =/ relay-card (send-card:global:nclient [%event event]) + + :: :: + :: =/ wid=@ -.u.rl + :: =/ relay=relay-stats:nsur +.u.rl + + =/ ui-fact [%nostr %sent-post host.p id.p ~[global-relay:constants] event] + :~ (update-ui:cards:lib ui-fact) + relay-card + == + + :: TODO anon feed :_ state - %+ weld mention-cards + %+ weld global-cards %+ weld mention-cards :~ ui-card fact-card == %quote + =/ quote ?- -.host.poke + %urbit [%ref %trill +.host.poke /(crip (scow:sr %ud id.poke))] + %nostr [%ref %nostr `@p`+.host.poke /(crip (scow:sr %ud id.poke))] + == =/ host (user-to-atom:lib host.poke) =/ sp (build-sp:postlib our.bowl our.bowl content.poke ~ ~) - =/ quote [%ref %trill host /(crip (scow:sr %ud id.poke))] =. contents.sp (snoc contents.sp quote) =/ p=post:post (build-post:postlib now.bowl pubkey sp) @@ -226,16 +249,18 @@ ?: ?=(%nostr -.host.poke) =/ mutan ~(. mutations-nostr [state bowl]) =/ rl get-relay:mutan - ?~ rl ~& >>> "no-relay!" `state + ?~ rl ~& >>> "no relay!" `state =/ wid=@ -.u.rl =/ relay=relay-stats:nsur +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) - =/ ev (build-event:evlib i.keys.state eny.bowl now.bowl content.poke) + =/ nclient ~(. nostr-client [state bowl]) + =/ rclient ~(. relay.nclient [wid relay]) + :: TODO look at that kind logic + =/ ev (build-event:evlib i.keys.state eny.bowl now.bowl content.poke 1) =/ parent-id (crip (scow:parsing:sr %ux id.poke)) =/ reply-tag=(list @t) ['e' parent-id url.relay 'reply' ~] =. tags.ev ~[reply-tag] :_ state - :~ (send:nclient url.relay [%event ev]) + :~ (send-card:rclient [%event ev]) == :: =/ host (user-to-atom:lib host.poke) diff --git a/app/lib/nostr/client.hoon b/app/lib/nostr/client.hoon index 2ed5b0a..e10cb08 100644 --- a/app/lib/nostr/client.hoon +++ b/app/lib/nostr/client.hoon @@ -1,158 +1,306 @@ /- sur=nostrill, nsur=nostr -/+ js=json-nostr, sr=sortug, seq, nostr-keys, constants, server, ws=websockets +/+ js=json-nostr, sr=sortug, seq, nostr-keys, constants, server, ws=websockets, evlib=nostr-events /= web /web/router -|_ [=state:sur =bowl:gall wid=@ud relay=relay-stats:nsur] - +|_ [=state:sur =bowl:gall] +$ card card:agent:gall +:: general utils ++ parse-msg |= [eyre-id=@ta req=inbound-request:eyre] ^- (unit relay-msg:nsur) ?~ body.request.req ~ =/ jstring q.u.body.request.req (parse-body jstring) - ++ parse-body |= jstring=@t =/ ures (de:json:html jstring) ?~ ures ~ =/ ur (relay-msg:de:js u.ures) ?~ ur ~& >>> relay-msg-parsing-failed=jstring ~ ur -:: __ - -++ close-sub |= [sub-id=@t wid=@ud relay=relay-stats:nsur] - ^- (quip card _relay) +++ close-sub-req |= [sub-id=@t wid=@ud relay=relay-stats:nsur] + ^- [client-msg:nsur relay-stats:nsur] =. reqs.relay (~(del by reqs.relay) sub-id) =/ req=client-msg:nsur [%close sub-id] - :- :~ (send url.relay req) == relay + [req relay] + +++ build-req +|= fs=(list filter:nsur) +^- [%req relay-req:nsur] + =/ sub-id (gen-sub-id:nostr-keys eny.bowl) + =/ msg [%req sub-id fs] + msg +++ init-req +|= [name=@t fs=(list filter:nsur) ongoing=(unit ?) chunked=(list filter:nsur)] + ^- req-state:nsur + =/ req=req-state:nsur [name fs 0 ongoing chunked] + req + +++ req-to-msg |= req=client-msg:nsur ^- websocket-message:eyre + =/ req-body=json (req:en:js req) + =/ octs (json-to-octs:server req-body) + =/ wmsg=websocket-message:eyre [1 `octs] + wmsg -++ send-req |= [fs=(list filter:nsur) ongoing=(unit ?) chunked=(list filter:nsur)] - ^- (quip card _relay) +++ set-req + |= [relay=relay-stats:nsur name=@t fs=(list filter:nsur) ongoing=(unit ?) chunked=(list filter:nsur)] + ^- [client-msg:nsur relay-stats:nsur] =/ sub-id (gen-sub-id:nostr-keys eny.bowl) - =/ req=client-msg:nsur [%req sub-id fs] - =/ es=event-stats:nsur [fs 0 ongoing chunked] - =/ url url.relay - =. reqs.relay (~(put by reqs.relay) sub-id es) - ~& > sending-ws-req=sub-id - :- :~ (send url req) == relay - - -++ get-posts - =/ kinds (silt ~[1]) - :: =/ last-week (sub now.bowl ~d7) - =/ last-week (sub now.bowl ~m1) - :: =/ since (to-unix-secs:jikan:sr last-week) - =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] - (send-req ~[filter] `.n ~) -:: -++ get-user-feed - |= pubkey=@ux - =/ kinds (silt ~[1]) - :: =/ since (sub now.bowl ~d30) - =/ since (sub now.bowl ~d5) - =/ pubkeys (silt ~[pubkey]) - =/ =filter:nsur [~ `pubkeys `kinds ~ `since ~ ~] - (send-req ~[filter] `.n ~) - -++ get-thread |= id=@ux - =/ kinds (silt ~[1]) - =/ ids (silt :~(id)) - =/ f1=filter:nsur [`ids ~ `kinds ~ ~ ~ ~] - =/ ids=(list @t) :~((crip (scow:parsing:sr %ux id))) - =/ tag ['e' ids] - =/ tags=(map @t (list @t)) (malt :~(tag)) - =/ f2=filter:nsur [~ ~ `kinds `tags ~ ~ ~] - ~& >>> getting-thread=[f1 f2] - (send-req ~[f1 f2] `.n ~) - -++ get-post |= id=@ux - =/ kinds (silt ~[1]) - =/ ids (silt :~(id)) - =/ =filter:nsur [`ids ~ `kinds ~ ~ ~ ~] - (send-req ~[filter] ~ ~) - -++ get-replies |= id=@ux - =/ kinds (silt ~[1]) - =/ ids=(list @t) :~((crip (scow:parsing:sr %ux id))) - =/ tag ['e' ids] - =/ tags=(map @t (list @t)) (malt :~(tag)) - =/ =filter:nsur [~ ~ `kinds `tags ~ ~ ~] - (send-req ~[filter] `.n ~) -:: -++ get-profile |= pubkey=@ux - =/ kinds (silt ~[0]) - :: =/ since (to-unix-secs:jikan:sr last-week) - =/ pubkeys (silt ~[pubkey]) - =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] - (send-req ~[filter] ~ ~) - -++ get-profiles + =/ msg=client-msg:nsur [%req sub-id fs] + =/ req=req-state:nsur [name fs 0 ongoing chunked] + =. reqs.relay (~(put by reqs.relay) sub-id req) + [msg relay] + +++ global + |% + ++ get-wid ^- (unit @) + =/ usocket (check-connected:ws global-relay:constants bowl) + ?~ usocket ~ + %- some -.u.usocket + :: ?~ global-relay-conn.state ~& >>> "not connected to global relay" !! + :: u.global-relay-conn.state + :: + :: TODO not fucking working + :: guess it doesn't await for %pending to resolve properly + ++ send-and-close |= req=client-msg:nsur ^- card + =/ wmsg (req-to-msg req) + (one-off:ws global-relay:constants wmsg bowl) + + + ++ send-card |= req=client-msg:nsur ^- card + =/ wmsg (req-to-msg req) + =/ wid get-wid + ?~ wid ~& >>> "not connected to global relay, running thread" + (send-and-close req) + (give-ws-payload-client:ws u.wid wmsg) + + :: filter builders + ++ get-profiles-from-global + ^- (list card) + ~& >>> "getting profiles from global" + =/ kinds (silt ~[0]) + =/ =filter:nsur [~ ~ `kinds ~ ~ ~ ~] + :: =/ req-name 'fetch all user profiles' + =/ req (build-req ~[filter]) + :~ (send-card req) + == + :: Doing this directly from frontend as relaying ws messages through backend is rather pointless + :: global feed + :: ++ get-global + :: =/ kinds (silt ~[667]) + :: =/ since ~ + :: =/ =filter:nsur [~ ~ `kinds ~ ~ ~ ~] + :: =/ req-name 'global feed' + :: + + -- +++ relay + |_ [wid=@ud relay=relay-stats:nsur] + :: ++ test-connection + :: =/ kinds (silt ~[1]) + :: =/ since (sub now.bowl ~m10) + :: =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~] + :: =/ sub-id (gen-sub-id:nostr-keys eny.bowl) + :: =/ req=client-msg:nsur [%req sub-id ~[filter]] + :: :- :~ (send url.relay req) == relay + + ++ send-card |= req=client-msg:nsur ^- card + =/ wmsg (req-to-msg req) + (give-ws-payload-client:ws wid wmsg) + :: + :: TODO temp, replace with one below + ++ get-posts-ted + ^- [sub-id=@t rs=req-state:nsur =card] + =/ kinds (silt ~[1]) + =/ last-week (sub now.bowl ~m1) + :: =/ since (to-unix-secs:jikan:sr last-week) + =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] + =/ req-name 'timeline' + =/ filters ~[filter] + =/ req (build-req filters) + =/ sub-id=@t +<.req + =/ card (send-card req) + =/ rs=req-state:nsur (init-req req-name filters ~ ~) + [sub-id rs card] + + + ++ get-posts + =/ kinds (silt ~[1]) + =/ last-week (sub now.bowl ~h1) + :: =/ since (to-unix-secs:jikan:sr last-week) + =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] + =/ req-name 'timeline' + =^ req relay (set-req relay req-name ~[filter] ~ ~) + :_ relay + :~ (send-card req) + == + :: ++ get-posts + :: =/ kinds (silt ~[1]) + :: =/ last-week (sub now.bowl ~d7) + :: :: =/ since (to-unix-secs:jikan:sr last-week) + :: =/ =filter:nsur [~ ~ `kinds ~ `last-week ~ ~] + :: =/ req-name 'timeline' + :: =^ req relay (set-req relay req-name ~[filter] `.n ~) + :: :_ relay + :: :~ (send-card req) + :: == + :: + ++ get-user-feed + |= pubkey=@ux + =/ kinds (silt ~[1]) + :: =/ since (sub now.bowl ~d30) + =/ since (sub now.bowl ~d1) + =/ pubkeys (silt ~[pubkey]) + =/ =filter:nsur [~ `pubkeys `kinds ~ `since ~ ~] + =/ req-name 'user sub' + =^ req relay (set-req relay req-name ~[filter] `.n ~) + :_ relay + :~ (send-card req) + == + ++ unfollow-user + |= pubkey=@ux + =/ reqs ~(tap by reqs.relay) + |- ?~ reqs `relay + =/ sub-id=@t -.i.reqs + =/ rs=req-state:nsur +.i.reqs + ?. (is-specific-user-sub:evlib pubkey filters.rs) + $(reqs t.reqs) + :: + =^ req relay (close-sub-req sub-id wid relay) + :_ relay + :~ (send-card req) + == + + ++ get-thread |= id=@ux + =/ kinds (silt ~[1]) + =/ ids (silt :~(id)) + =/ f1=filter:nsur [`ids ~ `kinds ~ ~ ~ ~] + =/ ids=(list @t) :~((crip (scow:parsing:sr %ux id))) + =/ tag ['e' ids] + =/ tags=(map @t (list @t)) (malt :~(tag)) + =/ f2=filter:nsur [~ ~ `kinds `tags ~ ~ ~] + ~& >>> getting-thread=[f1 f2] + =/ req-name 'thread sub' + =^ req relay (set-req relay req-name ~[f1 f2] `.n ~) + :_ relay + :~ (send-card req) + == + + ++ get-post |= id=@ux + =/ kinds (silt ~[1]) + =/ ids (silt :~(id)) + =/ =filter:nsur [`ids ~ `kinds ~ ~ ~ ~] + =/ req-name 'fetch post' + =^ req relay (set-req relay req-name ~[filter] ~ ~) + :_ relay + :~ (send-card req) + == + + ++ get-replies |= id=@ux + =/ kinds (silt ~[1]) + =/ ids=(list @t) :~((crip (scow:parsing:sr %ux id))) + =/ tag ['e' ids] + =/ tags=(map @t (list @t)) (malt :~(tag)) + =/ =filter:nsur [~ ~ `kinds `tags ~ ~ ~] + =/ req-name 'post replies sub' + =^ req relay (set-req relay req-name ~[filter] `.n ~) + :_ relay + :~ (send-card req) + == + :: + ++ get-profile |= pubkey=@ux + =/ kinds (silt ~[0]) + :: =/ since (to-unix-secs:jikan:sr last-week) + =/ pubkeys (silt ~[pubkey]) + =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] + =/ req-name 'user profile fetch' + =^ req relay (set-req relay req-name ~[filter] ~ ~) + :_ relay + :~ (send-card req) + == + + ++ get-profiles + ^- (quip card relay-stats:nsur) ~& >>> "getting profiles" =/ npoasts (tap:norm:sur nostr-feed.state) + =/ req-name 'user profiles fetch' =| missing-profs=(set @ux) =/ pubkeys=(set @ux) |- ?~ npoasts missing-profs - =/ poast=event:nsur +.i.npoasts + =/ poast=event:nsur +>.i.npoasts =/ have (~(has by profiles.state) [%nostr pubkey.poast]) =? missing-profs !have (~(put in missing-profs) pubkey.poast) $(npoasts t.npoasts) =/ kinds (silt ~[0]) =/ chunk-size 300 ~& >> fetching-profiles=~(wyt in pubkeys) + =^ req relay ?. (gth ~(wyt in pubkeys) chunk-size) =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] - (send-req ~[filter] ~ ~) + (set-req relay req-name ~[filter] ~ ~) :: =/ chunks=(list (list @ux)) (chunk-by-size:seq ~(tap in pubkeys) chunk-size) - ?~ chunks ~& >>> "error chunking pubkeys" `relay + ?~ chunks ~& >>> "error chunking pubkeys" !! =/ queue=(list filter:nsur) %+ turn t.chunks |= l=(list @ux) ^- filter:nsur =/ pubkeys=(set @ux) (silt l) [~ `pubkeys `kinds ~ ~ ~ ~] =/ pubkeys=(set @ux) (silt i.chunks) =/ =filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~] - (send-req ~[filter] ~ queue) + (set-req relay req-name ~[filter] ~ queue) + :_ relay + :~ (send-card req) + == -++ get-engagement - |= post-ids=(set @ux) - =/ post-strings %+ turn ~(tap in post-ids) |= id=@ux (crip (scow:sr %ux id)) - =/ =filter:nsur - =/ kinds (silt ~[6 7]) - =/ tags (malt :~([%e post-strings])) - [~ ~ `kinds `tags ~ ~ ~] - (send-req ~[filter] `.n ~) + ++ get-engagement + |= post-ids=(set @ux) + =/ post-strings %+ turn ~(tap in post-ids) |= id=@ux (crip (scow:sr %ux id)) + =/ =filter:nsur + =/ kinds (silt ~[6 7]) + =/ tags (malt :~([%e post-strings])) + [~ ~ `kinds `tags ~ ~ ~] + =/ req-name 'post engagement sub' + =^ req relay (set-req relay req-name ~[filter] `.n ~) + :_ relay + :~ (send-card req) + == -++ get-quotes - |= post-id=@ux - =/ post-string (crip (scow:sr %ux post-id)) - =/ kinds (silt ~[1]) - =/ tags (malt :~([%q ~[post-string]])) - =/ =filter:nsur [~ ~ `kinds `tags ~ ~ ~] - (send-req ~[filter] `.n ~) + ++ get-quotes + |= post-id=@ux + =/ post-string (crip (scow:sr %ux post-id)) + =/ kinds (silt ~[1]) + =/ tags (malt :~([%q ~[post-string]])) + =/ =filter:nsur [~ ~ `kinds `tags ~ ~ ~] + =/ req-name 'post quotes sub' + =^ req relay (set-req relay req-name ~[filter] `.n ~) + :_ relay + :~ (send-card req) + == -:: -++ test-connection - =/ kinds (silt ~[1]) - =/ since (sub now.bowl ~m10) - =/ =filter:nsur [~ ~ `kinds ~ `since ~ ~] - =/ sub-id (gen-sub-id:nostr-keys eny.bowl) - =/ req=client-msg:nsur [%req sub-id ~[filter]] - :- :~ (send url.relay req) == relay - -++ send - |= [relay-url=@t req=client-msg:nsur] ^- card - ~& >>> sendws=[relay-url req] - =/ req-body=json (req:en:js req) - =/ octs (json-to-octs:server req-body) - =/ wmsg=websocket-message:eyre [1 `octs] - =/ conn (check-connected:ws relay-url bowl) - ~& >>> send-client-conn=conn - ?~ conn :: if no ws connection we start a thread which will connect first, then send the message - ~& >>> "no connection!!" - !! - :: =/ =task:iris [%websocket-connect dap.bowl relay-url] - :: [%pass /ws-req/nostrill %arvo %i task] - :: - (give-ws-payload-client:ws wid.u.conn wmsg) + ++ get-follows + |= pubkey=@ux + =/ =filter:nsur + =/ kinds (silt ~[3]) + =/ authors (silt ~[pubkey]) + [~ `authors `kinds ~ ~ ~ ~] + =/ req-name 'user follows fetch' + =^ req relay (set-req relay req-name ~[filter] ~ ~) + :_ relay + :~ (send-card req) + == + + ++ get-followers + |= pubkey=@ux + =/ pubkeys (crip (scow:parsing:sr %ux pubkey)) + =/ =filter:nsur + =/ kinds (silt ~[3]) + =/ tags (malt :~([%p ~[pubkeys]])) + [~ ~ `kinds `tags ~ ~ ~] + :: TODO probably will need to chunk? + =/ req-name 'user followers sub' + =^ req relay (set-req relay req-name ~[filter] `.n ~) + :_ relay + :~ (send-card req) + == + -- -- diff --git a/app/lib/nostr/events.hoon b/app/lib/nostr/events.hoon index dccce1f..4553025 100644 --- a/app/lib/nostr/events.hoon +++ b/app/lib/nostr/events.hoon @@ -15,6 +15,28 @@ == .y $(fs t.fs) +++ is-user-sub |= fs=(list filter:nsur) ^- ? + |- ?~ fs .n + =/ filter i.fs + ?~ kinds.filter .n + ?~ authors.filter .n + ?: (~(has in u.kinds.filter) 0) .n + ?: ?& (~(has in u.kinds.filter) 1) + ?=(%~ ids.filter) + == .y + $(fs t.fs) + +++ is-specific-user-sub |= [pubkey=@ux fs=(list filter:nsur)] ^- ? + =/ must-kinds (silt ~[1]) + =/ must-authors (silt ~[pubkey]) + |- ?~ fs .n + =/ filter i.fs + ?: ?& .=(`must-kinds kinds.filter) + .=(`must-authors authors.filter) + ?=(%~ ids.filter) + == .y + $(fs t.fs) + ++ profs-req |= fs=(list filter:nsur) ^- ? |- ?~ fs .n =/ filter i.fs @@ -107,9 +129,10 @@ =? ids ?=(^ ref) (~(put in ids) u.ref) $(tags t.tags) -++ build-event |= [=keys:nsur eny=@ time=@da content=@t] ^- event:nsur +++ build-event |= [=keys:nsur eny=@ time=@da content=@t kind=@ud] ^- event:nsur + :: kind should be 1 when sending to normal relays, 667 to sending to the global relay =/ ts (to-unix-secs:jikan:sr time) - =/ raw=raw-event:nsur [pub.keys ts 1 ~ content] + =/ raw=raw-event:nsur [pub.keys ts kind ~ content] =/ event-id (hash-event:nostr-keys raw) =/ signature (sign-event:nostr-keys priv.keys event-id eny) ~& hash-and-signed=[event-id signature] @@ -124,11 +147,32 @@ == event -++ post-to-event |= [=keys:nsur eny=@ p=post:post] ^- event:nsur +++ profile-to-event |= [=keys:nsur prof=user-profile:comms eny=@ now=@da] + ^- event:nsur + :: kind should be 1 when sending to normal relays, 667 to sending to the global relay + =/ jon=json (user-meta:en:js +>+>+.prof) + =/ string (en:json:html jon) + =/ ts (to-unix-secs:jikan:sr now) + =/ raw=raw-event:nsur [pub.keys ts 0 ~ string] + =/ event-id (hash-event:nostr-keys raw) + =/ signature (sign-event:nostr-keys priv.keys event-id eny) + ~& hash-and-signed=[event-id signature] + =/ =event:nsur :* + event-id + pub.keys + created-at.raw + kind.raw + tags.raw + content.raw + signature + == + event +++ post-to-event |= [=keys:nsur eny=@ p=post:post kind=@ud tags=(list tag:nsur)] ^- event:nsur + :: kind should be 1 when sending to normal relays, 667 to sending to the global relay =/ cl (latest-post-content:trill contents.p) =/ string (crip (content-list-to-md:trill cl)) =/ ts (to-unix-secs:jikan:sr id.p) - =/ raw=raw-event:nsur [pub.keys ts 1 ~ string] + =/ raw=raw-event:nsur [pub.keys ts kind tags string] =/ event-id (hash-event:nostr-keys raw) =/ signature (sign-event:nostr-keys priv.keys event-id eny) ~& hash-and-signed=[event-id signature] @@ -144,7 +188,7 @@ event ++ event-to-post - |= [=event:nsur profile=(unit user-meta:nsur) relay=(unit @t)] + |= [=event:nsur profile=(unit user-profile:comms) relay=(unit @t)] ^- post-wrapper:comms :: most people on nostr don't use markdown, they just spam links like retards =/ cl (tokenize:trill content.event) diff --git a/app/lib/nostrill.hoon b/app/lib/nostrill.hoon index 75b5d0e..02df8e9 100644 --- a/app/lib/nostrill.hoon +++ b/app/lib/nostrill.hoon @@ -1,11 +1,13 @@ /- sur=nostrill, nsur=nostr, comms=nostrill-comms, ui=nostrill-ui, post=trill-post, gate=trill-gate -/+ trill=trill-post, nostr-keys, sr=sortug, jsonlib=json-nostrill, +/+ trill=trill-post, nostr-keys, sr=sortug, + jsonlib=json-nostrill, + constants, ws=websockets |% :: ++ default-state |= =bowl:gall ^- state:sur - =/ s *state-0:sur + =/ s *state:sur :: =/ l ~['wss://relay.damus.io' 'wss://nos.lol'] =/ key (gen-keys:nostr-keys eny.bowl) =/ keyl [key ~] @@ -21,15 +23,14 @@ ~& sub-count=~(wyt by reqs.rs) =/ total-received %+ roll ~(tap by reqs.rs) - |= [[* es=event-stats:nsur] acc=@ud] - %+ add acc received.es + |= [[* rs=req-state:nsur] acc=@ud] + %+ add acc received.rs ~& >> total=total-received $(l t.l) ++ ui-ws-res |= [wid=@ msg=@t] - =/ resmsg (cat 3 msg (cat 3 msg msg)) - =/ octs (as-octs:mimes:html resmsg) + =/ octs (as-octs:mimes:html msg) =/ res-event=websocket-event:eyre [%message 1 `octs] :~ (give-ws-payload-server:ws wid res-event) == @@ -52,6 +53,12 @@ ++ cards |_ =bowl:gall + ++ init ^- (list card:agent:gall) + :: :- global-relay-card + bindings + ++ global-relay-card ^- card:agent:gall + (connect:ws global-relay:constants bowl) + ++ relay-binding ^- card:agent:gall [%pass /binding %arvo %e %connect [~ /nostrill] dap.bowl] ++ ui-binding ^- card:agent:gall @@ -69,5 +76,14 @@ :: ++ poke-host |= [sip=@p =poke:comms] ^- card:agent:gall [%pass /heads-up %agent [sip dap.bowl] %poke %noun !>(poke)] + + ++ poke-thread |= [tid=@ta body=*] ^- card:agent:gall + =/ ta-now (scot %ud `@`now.bowl) + [%pass /ted-res/[ta-now] %agent [our.bowl %spider] %poke %spider-input !>([tid %noun !>(body)])] + + ++ poke-ui-thread |= [tid=@ta sub-id=@t] ^- card:agent:gall + =/ ta-now (scot %ud `@`now.bowl) + =/ payload=ted:ui [%res sub-id] + [%pass /ted-res/[ta-now] %agent [our.bowl %spider] %poke %spider-input !>([tid %nostrill-ted !>(payload)])] -- -- diff --git a/app/lib/nostrill/comms.hoon b/app/lib/nostrill/comms.hoon deleted file mode 100644 index 274d143..0000000 --- a/app/lib/nostrill/comms.hoon +++ /dev/null @@ -1,109 +0,0 @@ -/- *wrap, sur=nostrill, nsur=nostr, comms=nostrill-comms, feed=trill-feed, post=trill-post, notif=nostrill-notif -/+ js=json-nostr, sr=sortug,constants, gatelib=trill-gate, feedlib=trill-feed, jsonlib=json-nostrill, lib=nostrill, mutations-trill, harklib=hark -|_ [=state:sur =bowl:gall] - -++ handle-req |= [=req:comms pat=path] - ^- (quip card:agent:gall _state) - =/ =user:sur [%urbit src.bowl] - =/ enreq=(enbowl req:comms) [user now.bowl req] - |^ - ?@ p.req :: %fans - (handle-feed-req %follow) - ?@ +.p.req - (handle-feed-req %beg) - (handle-thread-req id.+.p.req) - -:: -++ handle-thread-req |= id=@da - ^- (quip card:agent:gall _state) - =/ ted (get:orm:feed feed.state id) - ?~ ted :: invalid request, no notifications or response recording here :: TODO do we wanna record spam? - =/ =beg-res:comms [%thread id %ng] - =/ =res:comms ['no such thread' %begs beg-res] - :_ state (send-fact res) - :: - =/ can (can-access:gatelib src.bowl read.perms.u.ted msg.req bowl) - =/ =decision:sur ?: can - [now.bowl .y .n 'どうぞ'] - [now.bowl .n .n 'not allowed'] - =/ =ruling:sur [enreq read.perms.u.ted decision] - =. responses.state (put:ors:sur responses.state now.bowl ruling) - :: - =/ n=notif:notif [%req enreq `decision] - =/ hark-card=card:agent:gall (send-hark:harklib n bowl) - :: - ?. can - =/ =beg-res:comms [%thread id %ng] - =/ =res:comms [msg.decision %begs beg-res] - =/ crds (send-fact res) - :_ state [hark-card crds] - :: - :: - =/ fn (node-to-full:feedlib u.ted feed.state) - =/ =beg-res:comms [%thread id %ok fn ~] - =/ =res:comms [msg.decision %begs beg-res] - =/ crds (send-fact res) - :_ state [hark-card crds] -:: - ++ handle-feed-req |= t=$?(%follow %beg) - ^- (quip card:agent:gall _state) - ?: manual.feed-perms.state :: don't decide now, save it in requests and defer - defer-ruling - :: - - =/ can (can-access:gatelib src.bowl feed-perms.state msg.req bowl) - =/ =decision:sur ?: can - [now.bowl .y .n 'どうぞ'] - [now.bowl .n .n 'not allowed'] - - =/ =ruling:sur [enreq feed-perms.state decision] - =. responses.state (put:ors:sur responses.state now.bowl ruling) - :: - =/ n [%req enreq `decision] - =/ hark-card=card:agent:gall (send-hark:harklib n bowl) - - |^ - ?: can give-feed deny-feed - - ++ give-feed - :: - =/ lp latest-page:feedlib - =/ lp2 lp(count backlog.feed-perms.state) - =/ =fc:feed (lp2 feed.state) - =/ prof (~(get by profiles.state) [%urbit our.bowl]) - =/ =res:comms :- msg.decision ?: ?=(%follow t) - [%fols %ok fc prof] - [%begs %feed %ok fc prof] - =/ crds (send-fact res) - :_ state [hark-card crds] - :: - ++ deny-feed - =/ =res:comms :- msg.decision ?: ?=(%follow t) - [%fols %ng] - [%begs %feed %ng] - - =/ crds (send-fact res) - :_ state [hark-card crds] - -- - - ++ send-fact |= =res:comms ^- (list card:agent:gall) - =/ paths :~(pat) - ?: ?=(%fols -.p.res) - =/ f=fols-res:comms +.p.res - =/ cage [%noun !>([%fols f])] - =/ c1 [%give %fact paths cage] - :~(c1) - :: - =/ jon (res:en:jsonlib res) - =/ cage [%json !>(jon)] - =/ c1 [%give %fact paths cage] - =/ c2 [%give %kick paths ~] - :~(c1 c2) - :: - ++ defer-ruling - =. requests.state (put:orq:sur requests.state now.bowl req) - =/ n=notif:notif [%req enreq ~] - =/ hark-card=card:agent:gall (send-hark:harklib n bowl) - :_ state :~(hark-card) - -- --- diff --git a/app/lib/nostrill/follows.hoon b/app/lib/nostrill/follows.hoon index 6aad471..d4389fd 100644 --- a/app/lib/nostrill/follows.hoon +++ b/app/lib/nostrill/follows.hoon @@ -14,17 +14,23 @@ =/ rl get-relay:mutan ?~ rl ~& >>> "no relay!" `state =/ wid -.u.rl - =/ relay +.u.rl - =/ nclient ~(. nostr-client [state bowl wid relay]) + =/ relay=relay-stats:nsur +.u.rl + =/ relay-url=@t url.relay + =/ nclient ~(. nostr-client [state bowl]) + =/ rclient ~(. relay.nclient [wid relay]) :: TODO now or on receival? =. following.state (~(put by following.state) user *feed:feed) =/ graph (~(get by follow-graph.state) [%urbit our.bowl]) =/ follows ?~ graph (silt ~[user]) (~(put in u.graph) user) =. follow-graph.state (~(put by follow-graph.state) [%urbit our.bowl] follows) - - =^ cards relay (get-user-feed:nclient +.user) + =^ cards relay (get-user-feed:rclient +.user) =. relays.state (~(put by relays.state) wid relay) - [cards state] + :: ui + =/ profile (~(get by profiles.state) user) + =/ =fact:ui [%fols %new-nostr +.user profile ~[relay-url]] + =/ ui-card (update-ui:cards:lib fact) + :_ state + [ui-card cards] == ++ handle-del |= =user:sur ^- (quip card:agent:gall _state) @@ -33,14 +39,27 @@ ?~ graph `state =/ nset (~(del in u.graph) user) =. follow-graph.state (~(put by follow-graph.state) [%urbit our.bowl] nset) - :_ state - =/ =fact:ui [%fols %quit user] - =/ c1 (update-ui:cards:lib fact) - ?. ?=(%urbit -.user) :~(c1) + =/ =fact:ui [%fols %quit user] + =/ c1 (update-ui:cards:lib fact) + ?: ?=(%urbit -.user) ~& >> leaving=user =/ c2 (urbit-leave +.user) + :_ state :~(c1 c2) + :: nostr + =/ mutan ~(. mutations-nostr [state bowl]) + =/ rl get-relay:mutan + ?~ rl ~& >>> "no relay!" `state + =/ wid -.u.rl + =/ relay=relay-stats:nsur +.u.rl + =/ relay-url=@t url.relay + =/ nclient ~(. nostr-client [state bowl]) + =/ rclient ~(. relay.nclient [wid relay]) + =^ cards relay (unfollow-user:rclient +.user) + :_ state + :- c1 cards + ++ handle-res |= fr=fols-res:comms ^- (quip card:agent:gall _state) ~& >> handling-follow-res=fr @@ -55,13 +74,13 @@ =/ fd=feed-data:comms data.p.fr =. following.state (~(put by following.state) user feed.fc.fd) =. following2.state (add-new-feed:feedlib following2.state feed.fc.fd) - =? profiles.state ?=(^ profile.fd) (~(put by profiles.state) user u.profile.fd) + =. profiles.state (~(put by profiles.state) user profile.fd) =/ graph (~(get by follow-graph.state) [%urbit our.bowl]) =/ follows ?~ graph (silt ~[user]) (~(put in u.graph) user) =. follow-graph.state (~(put by follow-graph.state) [%urbit our.bowl] follows) state :: - =/ =fact:ui [%fols %new enfr] + =/ =fact:ui [%fols %new-urbit enfr] =/ ui-card (update-ui:cards:lib fact) :_ state :~ diff --git a/app/lib/scri.hoon b/app/lib/scri.hoon index bc50cde..dc4a053 100644 --- a/app/lib/scri.hoon +++ b/app/lib/scri.hoon @@ -11,6 +11,57 @@ |_ [=state:sur =bowl:gall] +$ card card:agent:gall +++ get-followers ^- (set user:comms) + =/ subs ~(tap by sup.bowl) + %+ roll subs |= [[* p=@p pat=path] acc=(set user:comms)] + ?. ?=([%follow ~] pat) acc + ?: .=(our.bowl p) acc + (~(put in acc) [%urbit p]) + + +++ my-meta-to-prof |= meta=user-meta:nsur ^- user-profile:comms + =/ fans get-followers + :- pub.i.keys.state + :- ~(key by following.state) + :- ~(wyt by following.state) + :- fans + :- ~(wyt in fans) + meta +++ user-meta-to-prof |= meta=user-meta:nsur ^- user-profile:comms + :- 0x0 + :- ~ + :- 0 + :- ~ + :- 0 + meta +++ default-profile ^- user-profile:comms + =/ fans get-followers + :* pub.i.keys.state + ~(key by following.state) + ~(wyt by following.state) + fans + ~(wyt in fans) + (scot %p our.bowl) + '' + '' + `our.bowl + ~ + == +++ empty-profile |= p=@p ^- user-profile:comms + :* 0x0 + ~ + 0 + ~ + 0 + (scot %p p) + '' + '' + `p + ~ + == + + + ++ get-poast |= [host=@p id=@] ^- (unit post:post) =/ poast ?: .=(host our.bowl) (get:orm:feed feed.state id) @@ -85,6 +136,9 @@ =/ ns=(unit @da) ?~ hed ~ (some key.u.hed) =/ ne=(unit @da) ?~ tal ~ (some key.u.tal) =/ =fc:feed [nf ns ne] - =/ profile (~(get by profiles.state) user) - =/ msg '' [%feed msg %done %ok fc profile] + =/ uprof (~(get by profiles.state) user) + =/ profile ?^ uprof u.uprof + ?: .=(our.bowl u.host) default-profile (empty-profile u.host) + =/ msg '' + [%feed msg %done %ok fc profile] -- diff --git a/app/lib/websockets.hoon b/app/lib/websockets.hoon index a87f2a1..8481452 100644 --- a/app/lib/websockets.hoon +++ b/app/lib/websockets.hoon @@ -1,9 +1,15 @@ +/+ sr=sortug, server |% ++ connect |= [endpoint=@t =bowl:gall] ^- card:agent:gall =/ =task:iris [%websocket-connect dap.bowl endpoint] [%pass /ws-connect %arvo %i task] + ++ wait-for-connection |= [endpoint=@t =bowl:gall] + =/ connect-card (connect endpoint bowl) + connect-card + + ++ cancel-connect |= wid=@ud ^- card:agent:gall =/ =task:iris [%cancel-websocket wid] @@ -11,25 +17,38 @@ ++ disconnect |= wid=@ud ^- card:agent:gall + ~& >>> disconnecting=wid =/ =path /websocket-client/(scot %ud wid) =/ ws-paths :~(path) [%give %fact ws-paths %disconnect !>(~)] - :: + :: + :: as client ++ give-ws-payload-client |= [wid=@ msg=websocket-message:eyre] + ~& > "sending-ws-to-client" + ~& wid + ~& msg=msg ^- card:agent:gall =/ =cage [%message !>(msg)] =/ wsid (scot %ud wid) [%give %fact ~[/websocket-client/[wsid]] cage] + + ++ close-ws-client |= wid=@ ^- card:agent:gall - =/ =cage - [%disconnect !>(~)] - =/ wsid (scot %ud wid) - [%give %fact ~[/websocket-client/[wsid]] cage] + =/ =cage + [%disconnect !>(~)] + =/ wsid (scot %ud wid) + [%give %fact ~[/websocket-client/[wsid]] cage] + ++ one-off + |= [endpoint=@t wmsg=websocket-message:eyre =bowl:gall] + ^- card:agent:gall + [%pass /ws-oneoff %arvo %k %fard dap.bowl %ws noun+!>([endpoint wmsg])] + :: + :: as server ++ give-ws-payload-server |= [wid=@ event=websocket-event:eyre] ^- card:agent:gall @@ -38,6 +57,39 @@ =/ wsid (scot %ud wid) [%give %fact ~[/websocket-server/[wsid]] cage] + ++ give-ws-payload-server-all + |= [=bowl:gall event=websocket-event:eyre] + ^- (list card:agent:gall) + =/ inc-subs ~(tap by sup.bowl) + %+ roll inc-subs |= [i=[=duct =ship pat=(pole knot)] acc=(list card:agent:gall)] + ?. ?=([%websocket-server wid=@ ~] pat.i) acc + =/ wid (slaw:sr %ud wid.pat.i) + ?~ wid acc + =/ =cage [%websocket-response !>([u.wid event])] + =/ card [%give %fact ~[pat.i] cage] + [card acc] + + ++ list-client-conns + |= =bowl:gall ^- (list path) + =/ inc-subs ~(tap by sup.bowl) + %+ roll inc-subs |= [i=[=duct =ship =path] acc=(list path)] + ?. ?=([%websocket-client *] path.i) acc + [path.i acc] + ++ list-server-conns + |= =bowl:gall ^- (list path) + =/ inc-subs ~(tap by sup.bowl) + %+ roll inc-subs |= [i=[=duct =ship =path] acc=(list path)] + ?. ?=([%websocket-server *] path.i) acc + [path.i acc] + + :: + ++ string-message + |= s=@t ^- websocket-message:eyre + =/ octs (json-to-octs:server %s s) + =/ wmsg=websocket-message:eyre [1 `octs] + wmsg + + ++ accept-handshake |= wid=@ =/ response [%accept ~] :~ @@ -52,14 +104,15 @@ +$ socket [wid=@ud url=@t status=$?(%accepted %pending)] ++ get-url |= [wid=@ud =bowl:gall] ^- (unit socket) - =/ scry-path=path /(scot %p our.bowl)/ws/(scot %da now.bowl)/id/(scot %ud wid) + =/ scry-path=path /(scot %p our.bowl)//(scot %da now.bowl)/ws/[dap.bowl]/id/(scot %ud wid) .^((unit socket) %ix scry-path) ++ check-connected |= [url=@t =bowl:gall] ^- (unit socket) - =/ scry-path=path /(scot %p our.bowl)/ws/(scot %da now.bowl)/url/[url] + =/ scry-path=path /(scot %p our.bowl)//(scot %da now.bowl)/ws/[dap.bowl]/url/[url] .^((unit socket) %ix scry-path) + ++ list-connected |= =bowl:gall ^- (list socket) - =/ scry-path=path /(scot %p our.bowl)/ws/(scot %da now.bowl)/app + =/ scry-path=path /(scot %p our.bowl)//(scot %da now.bowl)/ws/[dap.bowl] .^((list socket) %ix scry-path) -- diff --git a/app/mar/nostrill-ted.hoon b/app/mar/nostrill-ted.hoon new file mode 100644 index 0000000..3b8509e --- /dev/null +++ b/app/mar/nostrill-ted.hoon @@ -0,0 +1,11 @@ +/- ui=nostrill-ui +|_ t=ted:ui +++ grow + |% + ++ noun r + -- +++ grab + |% + ++ noun ted:ui + -- +-- diff --git a/app/sur/nostr.hoon b/app/sur/nostr.hoon index b8f1e7d..cde6ced 100644 --- a/app/sur/nostr.hoon +++ b/app/sur/nostr.hoon @@ -1,5 +1,9 @@ |% +$ keys [pub=@ priv=@] ++$ wevent + $: relays=(list @t) + event + == +$ event $: id=@ux :: 32bytes pubkey=@ux :: 32bytes @@ -25,15 +29,18 @@ $: pubkey=@ux :: 32bytes $: name=@t about=@t picture=@t + patp=(unit @p) other=(map @t json) == + +$ relay-stats $: start=@da url=@t - reqs=(map sub-id event-stats) + reqs=(map sub-id req-state) == -+$ event-stats -$: filters=(list filter) ++$ req-state +$: name=@t :: for frontend purposes, defined by the app + filters=(list filter) received=event-count :: if not ongoing we kill the subscription on %eose. If ongoing we turn to .y after %eose ongoing=(unit ?) diff --git a/app/sur/nostrill.hoon b/app/sur/nostrill.hoon index 6292407..bf6ac88 100644 --- a/app/sur/nostrill.hoon +++ b/app/sur/nostrill.hoon @@ -6,6 +6,8 @@ $: %0 :: nostr config relays=(map @ud relay-stats:nostr) :: key is the websocket id + :: TODO deprecate? + global-relay-conn=(unit @ud) :: the websocket id :: ws-msg-queue=(list websocket-event:eyre) keys=(lest keys:nostr) :: cycled, i.keys is current one :: own feed @@ -15,9 +17,10 @@ :: TODO deprecate and parse properly into a feed:trill =nostr-feed :: profiles - profiles=(map user user-meta:nostr) + profiles=(map user user-profile:comms) following=(map user =feed:trill) following2=global-feed + :: =global-feed follow-graph=(map user (set user)) :: Save incoming requests to handle async @@ -32,8 +35,8 @@ +$ upid [=user id=@da] ++ ugth |= [a=[[* id=@] =time] b=[[* id=@] =time]] ?: .=(time.a time.b) (gth id.a id.b) (gth time.a time.b) -+$ nostr-feed ((mop @ud event:nostr) gth) -++ norm ((on @ud event:nostr) gth) ++$ nostr-feed ((mop @ud wevent:nostr) gth) +++ norm ((on @ud wevent:nostr) gth) +$ nfc [feed=nostr-feed start=cursor:trill end=cursor:trill] +$ user $%([%urbit p=@p] [%nostr p=@ux]) diff --git a/app/sur/nostrill/comms.hoon b/app/sur/nostrill/comms.hoon index 776ffa7..637f6c5 100644 --- a/app/sur/nostrill/comms.hoon +++ b/app/sur/nostrill/comms.hoon @@ -5,14 +5,27 @@ +$ upid [=user id=@da] +$ user $%([%urbit p=@p] [%nostr p=@ux]) ++$ user-profile + $: pubkey=@ux + following=(set user) + following-count=@ud + followers=(set user) + follower-count=@ud + user-meta:nsur + == :: Pokes are used to notify solely users of engagement. There is no data requests through pokes +$ poke $% [%eng engagement] + [%ted wid=@ud ws-proxy] [%dbug *] == ++$ ws-proxy + $% [%msg msg=websocket-message:eyre] + [%disconnect ~] + == +$ engagement $% [%reply parent=@da child=post:tp] [%quote src=@da =post:tp] @@ -45,7 +58,7 @@ == +$ fols-res (deferred feed-data) -+$ feed-data [=fc:tf profile=(unit user-meta:nsur)] ++$ feed-data [=fc:tf profile=user-profile] +$ thread-data $: node=full-node:tp thread=(list full-node:tp) :: list of all the users consecutive posts, as in long form thread @@ -54,13 +67,13 @@ +$ fact $% [%feed fols-res] :: response to follow requests [%post post-fact] - [%prof prof-fact] + [%prof (unit user-profile)] :: null to delete the profile == :: We wrap posts on nostr metadata if the post was also published to Nostr +$ post-wrapper [=post:tp =nostr-meta] +$ nostr-meta $: pub=@ux - prof=(unit user-meta:nsur) + prof=(unit user-profile) ev-id=(unit @ux) relays=(list @t) == @@ -70,9 +83,4 @@ $: pub=@ux [%upd post-wrapper] [%del post-wrapper] == -:: Updates on your user profile, or if Nostr keys have changed -+$ prof-fact - $% [%prof =user-meta:nsur] - [%keys pub=@ux] - == -- diff --git a/app/sur/nostrill/ui.hoon b/app/sur/nostrill/ui.hoon index b752888..c5cf1b8 100644 --- a/app/sur/nostrill/ui.hoon +++ b/app/sur/nostrill/ui.hoon @@ -14,7 +14,7 @@ [%thread p=@p id=@da] == +$ post-poke - $% [%add content=@t] + $% [%add content=@t global=? anon=?] [%reply content=@t host=user:sur id=@da thread=@da] [%quote content=@t host=user:sur id=@da] [%rp host=user:sur id=@da] :: NIP-18 @@ -32,38 +32,54 @@ [%fetch p=(list user:sur)] == +$ relay-poke - $% [%add p=@t] + :: add or remove relays + $% [%add p=@t] [%del p=@ud] - :: - relay-handling + :: data to send/receive from relays + [%do relay-handling] == +$ relay-handling + $: relays=(list @ud) :: list of wids + action=relay-order + == ++$ relay-order + $% relay-get + [%send-post host=@p id=@da] + [%send-prof ~] + == ++$ relay-get $% [%sync ~] [%prof ~] [%user pubkey=@ux] [%thread id=@ux] - :: send event for... relaying - [%send host=@p id=@ relays=(list @t)] == :: facts +$ fact $% [%nostr nostr-fact] [%post post-fact:comms] [%fols fols-fact] - [%prof (map user:sur user-meta:nsur)] + [%prof (map user:sur user-profile:comms)] :: our own keys! [%keys pub=@ux] == +$ fols-fact - $% [%new (enbowl fols-res:comms)] + $% [%new-urbit (enbowl fols-res:comms)] + [%new-nostr pubkey=@ux profile=(unit user-profile:comms) relays=(list @t)] :: UI feedback that the backend handled the click [%quit =user:sur] == +$ nostr-fact $% [%feed feed=nostr-feed:sur] - [%user feed=nostr-feed:sur] - [%thread feed=nostr-feed:sur] - [%event event:nsur] + [%user feed=nostr-feed:sur] :: a user feed we requested + [%thread feed=nostr-feed:sur] :: a specific thread we requested + [%event event:nsur] :: some specific event + [%eose sub-id=@t] :: end of data or backlog [%relays (map @ relay-stats:nsur)] + [%sent-post host=@p id=@ relays=(list @t) event:nsur] :: confirmation that a post of ours was sent to a relay + [%sent-prof relays=(list @t)] == ++$ ted + $% [%req tid=@t relays=(list @ud) p=relay-get] + [%res sub-id=@t] + == -- diff --git a/app/sys.kelvin b/app/sys.kelvin index 1ec239e..5a6f9d5 100644 --- a/app/sys.kelvin +++ b/app/sys.kelvin @@ -1 +1 @@ -[%zuse 410] +[%zuse 408] diff --git a/app/ted/fetch.hoon b/app/ted/fetch.hoon index e876511..50be0c3 100644 --- a/app/ted/fetch.hoon +++ b/app/ted/fetch.hoon @@ -5,8 +5,10 @@ =, strand-fail=strand-fail:libstrand:spider ^- thread:spider |= arg=vase - =/ request ;;(request:http q.arg) =/ m (strand ,vase) ^- form:m + =/ ureq %- (soft request:http) q.arg + ?~ ureq (pure:m !>('wtf')) + =/ request u.ureq :: =/ m (strand ,json) ^- form:m ;< ~ bind:m (send-request:strandio request) ;< res=client-response:iris bind:m take-client-response:strandio diff --git a/app/ted/sync.hoon b/app/ted/sync.hoon new file mode 100644 index 0000000..fbacef0 --- /dev/null +++ b/app/ted/sync.hoon @@ -0,0 +1,42 @@ +/- spider, ui=nostrill-ui +/+ strandio, jsonlib=json-nostrill, sr=sortug, lib=nostrill +=, strand=strand:spider +=, strand-fail=strand-fail:libstrand:spider +^- thread:spider +|= arg=vase + =/ m (strand ,vase) ^- form:m + |^ + =/ ujon !<((unit json) arg) + ~& >> sync-thread=ujon + ?~ ujon bail + =/ ureq (de-relay-do:de:jsonlib u.ujon) + ?~ ureq ~& bad-json=(en:json:html u.ujon) bail + =/ req=relay-handling:ui u.ureq + ~& req=req + ;< =bowl:spider bind:m get-bowl:strandio + ?. ?=(relay-get:ui action.req) bail + =/ t=ted:ui [%req tid.bowl relays.req action.req] + + ;< ~ bind:m (poke-our:strandio %nostrill %nostrill-ted !>(t)) + ;< v=vase bind:m (take-poke:strandio %nostrill-ted) + ~& v=v + =/ res !<(ted:ui v) + ~& sub-id-to-ted=res + ?. ?=(%res -.res) bail + =/ j=json [%s sub-id.res] + (pure:m !>(j)) + :: ?+ -.action.req (pure:m !>(bail)) + :: %sync + :: ;< =bowl:spider bind:m get-bowl:strandio + :: =/ desk q.byk.bowl + + :: ~& > ship=ship + :: =/ =user:sur (atom-to-user:lib ship) + :: (pure:m !>(j)) + :: == + ++ bail + %- pure:m !> + ^- json + %+ frond:enjs:format %error + s+'error' + -- diff --git a/app/ted/ws.hoon b/app/ted/ws.hoon index ac8a672..3ffbcc1 100644 --- a/app/ted/ws.hoon +++ b/app/ted/ws.hoon @@ -1,55 +1,59 @@ -/- spider, nsur=nostr -/+ strandio +/- spider, ui=nostrill-ui, comms=nostrill-comms +/+ strandio, jsonlib=json-nostrill, sr=sortug, lib=nostrill, ws=websockets =, strand=strand:spider +=, strand-fail=strand-fail:libstrand:spider ^- thread:spider +:: One Off WebSockets message thread +:: Connets to WebSockets server, awaits the connection to be open, sends message, awaits confirmation, then closes connection |= arg=vase -=/ m (strand ,vase) -|^ -^- form:m -:: =/ [url=@t req=client-msg:nsur] (need !<((unit [@t client-msg:nsur]) arg)) -=/ ujon !<((unit json) arg) -:: ~& ujon=ujon -?~ ujon (pure:m !>(bail)) -:: =/ req (ui:de:jsonlib u.ujon) -=/ jstring (en:json:html u.ujon) -~& >> jstring=jstring -:: ;< =bowl:spider bind:m get-bowl:strandio -:: =/ desk q.byk.bowl -:: =/ =task:iris [%websocket-connect desk url] -:: =/ =card:agent:gall [%pass /ws-req/nostrill %arvo %i task] -:: ;< ~ bind:m (send-raw-card:strandio card) -:: ;< res=(pair wire sign-arvo) bind:m take-sign-arvo:strandio -:: ~& > res=res -:: :: confirm connection was established -:: ?. ?=([%iris %websocket-response id=@ud websocket-event:eyre] q.res) -:: (strand-fail:strand %bad-sign ~) -:: ~& > ted-ws-res=+>+<.q.res -:: ?. ?=(%accept +>+<.q.res) -:: (pure:m !>([%ng ''])) -:: :: (strand-fail:strand %bad-sign ~) + =/ m (strand ,vase) ^- form:m + ;< =bowl:spider bind:m get-bowl:strandio + =/ args !<([@t websocket-message:eyre] arg) + =/ endpoint -.args + |^ + + :: + =/ =task:iris [%websocket-connect q.byk.bowl endpoint] + =/ iris-card [%pass /ws-connect %arvo %i task] + ;< ~ bind:m (send-raw-card:strandio iris-card) + ;< wid=@ud bind:m %+ (retry:strandio @ud) `5 get-wid + ~& >> ted-found-wid=wid + :: NOTE: can't directly send cards to Iris, Iris is subscribed to the agent, not the Thread, hence won't receive them. Poke the agent instead + =/ pok=poke:comms [%ted wid %msg +.args] + ;< ~ bind:m (poke-our:strandio %nostrill %noun !>(pok)) + ;< ~ bind:m (sleep:strandio ~s2) -:: ~& "ws connection accepted, sending ws msg" -:: ~& >>> "sleeping" -:: ;< ~ bind:m (sleep:strandio ~s3) -:: ~& >>> "slept" -:: =/ card2=card:agent:gall -:: [%pass /ws/proxy %agent [our.bowl desk] %poke %websocket-thread !>([id.q.res wmsg])] -:: ;< ~ bind:m (send-raw-card:strandio card2) -:: ;< res2=(pair wire sign-arvo) bind:m take-sign-arvo:strandio + =/ pok=poke:comms [%ted wid %disconnect ~] + ;< ~ bind:m (poke-our:strandio %nostrill %noun !>(pok)) + + + + (pure:m !>(~)) + + :: == + +$ socket [wid=@ud url=@t status=$?(%accepted %pending)] + ++ get-wid + ~& > "hey hey getting wid" + =/ m (strand ,(unit @)) + ^- form:m + ;< sockets=(list socket) bind:m (scry:strandio (list socket) /ix/ws/app) + :: =/ sockets .^((list socket) %ix (scot %p our.bowl) %ws (scot %da now.bowl) /app) + ~& >>> get-wid=sockets + ?~ sockets (pure:m ~) + =/ l=(list socket) sockets + |- ?~ l (pure:m ~) + =/ =socket i.l + ?. .=(url.socket endpoint) + $(l t.l) + ?. ?=(%accepted status.socket) + $(l t.l) + (pure:m `wid.socket) -:: :: =/ subwire=path /websocket-server/(scot %ud id.q.res) -:: :: =/ =cage [%websocket-response !>(+>.q.res)] -:: :: =/ gf=gift:agent:gall [%fact :~(subwire) cage] -:: :: =/ =card:agent:gall [%give gf] -:: :: ~& >> ws-ted-ok-sending-msg=id.q.res -:: :: ;< ~ bind:m (send-raw-card:strandio card) -:: :: ;< res2=(pair wire sign-arvo) bind:m take-sign-arvo:strandio -:: :: ?. ?=([%iris %websocket-response id=@ud %message wm=websocket-message:eyre] q.res2) -:: :: (strand-fail:strand %bad-sign ~) -:: :: =/ wm=websocket-message:eyre +>+>.q.res2 - :: (pure:m !>([%ok id.q.res])) - ++ bail ^- json - %+ frond:enjs:format %error - s+'error' --- + ++ bail + %- pure:m !> + ^- json + %+ frond:enjs:format %error + s+'error' + + -- diff --git a/arvo/eyre.hoon b/arvo/eyre.hoon index 4d9e615..723ebba 100644 --- a/arvo/eyre.hoon +++ b/arvo/eyre.hoon @@ -3211,13 +3211,22 @@ ++ handle-ws-response |= [wid=@ event=websocket-event] ^- [(list move) server-state] - ~& eyre-handle-ws-esponse=[wid event] - :: TODO remove if not accepted? =. connections.state - ?. ?=(%reject -.event) connections.state - (~(del by connections.state) duct) + ?: ?=(%reject -.event) (~(del by connections.state) duct) + ?: ?=(%disconnect -.event) (~(del by connections.state) duct) + connections.state + =. sockets.state + ?: ?=(%reject -.event) (~(del by sockets.state) wid) + ?: ?=(%disconnect -.event) (~(del by sockets.state) wid) + ?: ?=(%accept -.event) + =/ 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) + sockets.state - :: TODO do all verification shit that handle-response is doing [[duct %give %websocket-response [wid event]]~ state] ++ handle-response @@ -4019,6 +4028,16 @@ 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) diff --git a/arvo/iris.hoon b/arvo/iris.hoon index f6a81f5..02b809c 100644 --- a/arvo/iris.hoon +++ b/arvo/iris.hoon @@ -21,16 +21,17 @@ :: card=(wind note gift) == -:: +note: private request from light to another vane +:: +note: private request from iris to another vane :: +$ note $% :: %d: to dill + :: $: %d :: :: - $% [%flog =flog:dill] - == == - :: + $% [%flog =flog:dill] + == + == [%g $>(%deal task:gall)] == -- @@ -38,10 +39,9 @@ :: |% +$ axle - $: :: date: date at which light's state was updated to this data structure + $: :: date: date at which iris state was updated to this data structure :: - date=%~2025.10.28 - :: date=%~2019.2.8 + date=%~2026.1.1 :: :: =state @@ -49,9 +49,10 @@ :: +state:client: state relating to open outbound HTTP connections :: +$ state - $: :: next-id: monotonically increasing id number for the next connection - :: UIP-125 + $: :: 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 @@ -91,6 +92,9 @@ :: expected-size: the expected content-length of the http request :: expected-size=(unit @ud) + :: request: the original request, needed for handling redirects + :: + request=request:http == -- :: @@ -127,11 +131,15 @@ =. connection-by-id.state %+ ~(put by connection-by-id.state) id =, outbound-config - [duct [redirects retries ~ ~ 0 ~]] + [duct [redirects retries ~ ~ 0 ~ request]] :: keep track of the duct for cancellation :: =. connection-by-duct.state (~(put by connection-by-duct.state) duct id) + :: if we don't have a duct yet just ignore the request, %born will + :: cancel it soon. this is not ideal to say the least. + :: + ?~ outbound-duct.state [~ state] :: start the download :: :: the original eyre keeps track of the duct on %born and then sends a @@ -154,7 +162,8 @@ ~& %iris-invalid-cancel [~ state] :: - :- [outbound-duct.state %give %cancel-request u.cancel-id]~ + :- ?~ outbound-duct.state ~ + [outbound-duct.state %give %cancel-request u.cancel-id]~ (cleanup-connection u.cancel-id) :: +receive: receives a response to an http-request we made :: @@ -191,7 +200,17 @@ `response-header:http-event :: [duct in-progress-http-request] - :: + =* status-code=@ud status-code.response-header.http-event + ?: ?| =(307 status-code) + =(303 status-code) + =(301 status-code) + == + %: handle-redirect + id + http-event + remaining-redirects.in-progress-http-request.u.connection + request.in-progress-http-request.u.connection + == ?: complete.http-event (send-finished id data.http-event) :: @@ -216,6 +235,29 @@ ~ == == + :: +handle-redirect: transparently handle redirects if applicable + :: + ++ handle-redirect + |= [id=@ud =http-event:http remaining-redirects=@ud =request:http] + ?> ?=(%start -.http-event) + ?: =(0 remaining-redirects) + ?: complete.http-event + (send-finished id data.http-event) + (record-and-send-progress id data.http-event) + ?~ loc=(get-header:http 'location' headers.response-header.http-event) + ?: complete.http-event + (send-finished id data.http-event) + (record-and-send-progress id data.http-event) + =. connection-by-id.state + %+ ~(jab by connection-by-id.state) id + |= [duct=^duct =in-progress-http-request] + :- duct + %= in-progress-http-request + remaining-redirects + (dec remaining-redirects.in-progress-http-request) + == + :_ state + [outbound-duct.state %give %request id request(url u.loc)]~ :: +record-and-send-progress: save incoming data and send progress report :: ++ record-and-send-progress @@ -258,7 +300,6 @@ =/ connection (~(got by connection-by-id.state) id) :: reassemble the octs that we've received into their final form :: - ~& iris-sending-finished=[id=id duct=duct connection=connection] =/ data=octs %- combine-octs %- flop @@ -293,18 +334,8 @@ connection-by-id (~(del by connection-by-id.state) id) connection-by-duct (~(del by connection-by-duct.state) duct.u.con) == - ++ 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) - == - - - :: outgoing websockets connection + :: UIP-125 + :: ++ ws-connect |= [desk=term url=@t] ~& iris-ws-connect=[desk url duct] @@ -321,7 +352,16 @@ :: This sends it to Vere to actually do the request :- [outbound-duct.state %give %websocket-handshake id url]~ state - :: + ++ 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] @@ -381,9 +421,10 @@ ++ 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] + [duct %pass /iris-ws-poke note] -- -- +:: end the =~ :: . == :: begin with a default +axle as a blank slate @@ -398,7 +439,8 @@ |% ++ call |= [=duct dud=(unit goof) wrapped-task=(hobo task)] - ^- [(list move) _light-gate] + ~> %spin.['call/iris'] + ^- [(list move) _iris-gate] :: =/ task=task ((harden task) wrapped-task) :: @@ -407,15 +449,15 @@ ?^ dud =/ moves=(list move) [[duct %slip %d %flog %crud [-.task tang.u.dud]] ~] - [moves light-gate] + [moves iris-gate] :: %trim: in response to memory pressure :: ?: ?=(%trim -.task) - [~ light-gate] + [~ iris-gate] :: %vega: notifies us of a completed kernel upgrade :: ?: ?=(%vega -.task) - [~ light-gate] + [~ iris-gate] :: =/ event-args [[eny duct now rof] state.ax] =/ client (per-client-event event-args) @@ -441,97 +483,147 @@ outbound-duct.state.ax duct == :: - [moves light-gate] + [moves iris-gate] :: %request =^ moves state.ax (request:client +.task) - [moves light-gate] + [moves iris-gate] :: %cancel-request =^ moves state.ax cancel:client - [moves light-gate] + [moves iris-gate] :: %receive =^ moves state.ax (receive:client +.task) - [moves light-gate] - :: + [moves iris-gate] :: UIP-125 + :: %websocket-connect =^ moves state.ax (ws-connect:client +.task) - [moves light-gate] + [moves iris-gate] %websocket-event =^ moves state.ax (ws-event:client +.task) - [moves light-gate] + [moves iris-gate] %cancel-websocket =^ moves state.ax (ws-cancel:client +.task) - [moves light-gate] + [moves iris-gate] == :: http-client issues no requests to other vanes -:: until now! :: ++ take |= [wire=(pole knot) =duct dud=(unit goof) hin=sign-arvo] - ^- [(list move) _light-gate] + ^- [(list move) _iris-gate] + ~> %spin.['take/iris'] ~& iris-take=[wire duct] ?< ?=(^ dud) - :_ light-gate + :_ 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 + ~& 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) - %+ welp movs - :~ [outbound-duct.state.ax %give %websocket-response wid %disconnect ~] - == - :: =/ =tang !<(tang q.cag) - :: :: %- (slog 'khan-fact' tang) - :: :: [hen %give %arow %| p.cag tang]~ - :: ~ - :: :: - :: %thread-done - :: :: [hen %give %arow %& %noun q.cag]~ - :: ~ + 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 ~] + == + :: =/ =tang !<(tang q.cag) + :: :: %- (slog 'khan-fact' tang) + :: :: [hen %give %arow %| p.cag tang]~ + :: ~ + :: :: + :: %thread-done + :: :: [hen %give %arow %& %noun q.cag]~ + :: ~ + == == == - == == :: -++ light-gate ..$ +++ iris-gate ..$ :: +load: migrate old state to new state (called on vane reload) :: ++ load - |= old=axle - :: |= old=* - ^+ ..^$ - :: - ~! %loading - :: ..^$(ax old) - ..^$ - + => |% + +$ axle-any + $% [date=%~2019.2.8 state=state-0] + [date=%~2025.7.17 state=state-1] + [date=%~2026.1.1 =state] + == + :: + +$ state-0 + $: next-id=@ud + connection-by-id=(map @ud [=duct in-progress-http-request=in-progress-http-request-0]) + connection-by-duct=(map duct @ud) + outbound-duct=duct + == + +$ in-progress-http-request-0 + $: remaining-redirects=@ud + remaining-retries=@ud + response-header=(unit response-header:http) + chunks=(list octs) + 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 + :: + 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 + =/ new-ax=axle + :- %~2026.1.1 + :- *(map @ud websocket-connection) + state.old + iris-gate(ax new-ax) + :: + %~2026.1.1 iris-gate(ax old) + == :: +stay: produce current state :: ++ stay `axle`ax @@ -541,6 +633,9 @@ ^- roon |= [lyc=gang pov=path car=term bem=beam] ^- (unit (unit cage)) + ~> %spin.['scry/iris'] + :: UIP-125 + :: scry state of open sockets ~& >> iris-scry=[lyc=lyc pov=pov car=car bem=bem syd=q.bem] ?. ?=(%x car) [~ ~] =/ caller +<.pov @@ -577,11 +672,13 @@ ?: .=(url.socket i.t.s.bem) `[id.socket url.socket status.socket] $(sockets t.sockets) == + :: /socket scry + :: =* ren car =* why=shop &/p.bem =* syd q.bem =* lot=coin $/r.bem - =* tyl s.bem + =* tyl s.bem :: ?. ?=(%& -.why) ~ =* his p.why @@ -594,5 +691,5 @@ axle+&+ax == ``mass+!>(maz) - [~ ~] - -- + [~ ~] +-- diff --git a/arvo/lull.hoon b/arvo/lull.hoon index da48e1f..3cfb8e7 100644 --- a/arvo/lull.hoon +++ b/arvo/lull.hoon @@ -4,7 +4,7 @@ => ..part ~% %lull ..part ~ |% -++ lull %322 +++ lull %321 :: :: :: :::: :: :: (1) models :: :: :: @@ -898,6 +898,8 @@ [%whey =spar boq=@ud] :: weight of noun bounded at .path.spar :: as measured by .boq [%gulp path] :: like %plug, but for |mesa + $>(%halt deep) :: halt flow after we hear a remote %flub + [%goad =ship] :: re-start flow after remote agent is %live == :: :: $gift: effect from ames @@ -1044,6 +1046,7 @@ [%kill =ship =bone] [%ahoy =ship =bone] :: XX remove bone; it's just next-bone.ossuary [%prun =ship =user=path =duct =ames=path] + [%halt =ship agent=term =bone] :: XX add [=agent=path cork=?] == :: $stun: STUN notifications, from unix :: @@ -1171,6 +1174,7 @@ keens=(map path keen-state) =chain tip=(jug =user=path [duct =ames=path]) + halt=(set bone) == +$ keen-state $+ keen-state @@ -1586,6 +1590,17 @@ ?: (lte size 8) [8 %0b10] [16 %0b11] :: + :: $axle: state for entire vane + :: + :: peers: states of connections to other ships + :: unix-duct: handle to give moves to unix + :: life: our $life; how many times we've rekeyed + :: rift: our $rift + :: bug: debug printing configuration + :: snub: blocklist for incoming packets + :: cong: parameters for marking a flow as clogged + :: dead: dead flow consolidation timer and recork timer, if set + :: +$ axle $: peers=(map ship ship-state) =unix=duct :: [//ames/0v0 ~] @@ -1598,7 +1613,7 @@ $: flow=[%flow (unit dead-timer)] :: ... for |ames chum=[%chum (unit dead-timer)] :: ... for |mesa cork=[%cork (unit dead-timer)] :: ... for %nacked corks - rots=[%rots (unit dead-timer)] :: ... fir expiring direct routes + rots=[%rots (unit dead-timer)] :: ... for expiring direct routes == :: =server=chain :: for serving %shut requests @@ -1708,6 +1723,11 @@ :: line: high-water mark for the last-acked message before migration :: line=@ud + :: a flow halts when: + :: - forward: %gall passes a %flub to %ames + :: - backward: a %plea gets %flubbed over the wire + :: + halt=?(%.y %.n) :: outbound %poke payloads, bounded in the ship's namespace :: always and only for requests :: @@ -3042,9 +3062,11 @@ :: notification that a cache entry has changed :: [%grow =path] + :: UIP-125 :: [%websocket-response wid=@ event=websocket-event] + == :: +$ task @@ -3111,12 +3133,15 @@ :: 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 @@ -3131,6 +3156,7 @@ [%disconnect ~] [%message message=websocket-message] == + :: +origin: request origin as specified in an Origin header :: +$ origin @torigin @@ -3683,7 +3709,8 @@ $% [%boon payload=*] :: ames response [%noon id=* payload=*] [%done error=(unit error:ames)] :: ames message (n)ack - [%flub ~] :: not ready to handle plea + [%flub agent=(unit term)] :: refuse to take plea + [%spur ~] :: ready to take plea [%unto p=unto] :: == :: +$ task :: incoming request @@ -3698,6 +3725,7 @@ [%doff dude=(unit dude) ship=(unit ship)] :: kill subscriptions [%rake dude=(unit dude) all=?] :: reclaim old subs [%lave subs=(list [?(%g %a) ship dude duct])] :: delete stale bitt(s) + $>(%halt deep:ames) :: send remote %flub $>(%init vane-task) :: set owner $>(%trim vane-task) :: trim state $>(%vega vane-task) :: report upgrade @@ -3728,9 +3756,9 @@ [%plot p=(unit plot) q=(map @ta farm)] == :: - +$ egg :: migratory agent state - $% [%nuke sky=(map spur @ud) cop=(map coop hutch)] :: see /sys/gall $yoke - $: %live + +$ egg :: migratory agent + $% [%nuke sky=(map spur @ud) cop=(map coop hutch)] :: state; see /sys/gall + $: %live :: $yoke control-duct=duct run-nonce=@t sub-nonce=@ @@ -3914,10 +3942,12 @@ :: %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 @@ -3940,6 +3970,7 @@ :: receives http data from outside :: [%receive id=@ud =http-event:http] + :: UIP-125 :: [%cancel-websocket id=@ud] @@ -3948,7 +3979,9 @@ :: receives websocket event from earth :: [%websocket-event id=@ud event=websocket-event:eyre] + == + :: UIP-125 :: +$ websocket-connection @@ -3958,6 +3991,7 @@ url=@t status=?(%pending %accepted) == + :: +client-response: one or more client responses given to the caller :: +$ client-response diff --git a/gui/.gitignore b/gui/.gitignore index 356ff08..2ab36dd 100644 --- a/gui/.gitignore +++ b/gui/.gitignore @@ -42,3 +42,6 @@ devenv.local.nix # pre-commit .pre-commit-config.yaml +public +glob.js +contacts.json diff --git a/gui/.npmrc b/gui/.npmrc new file mode 100644 index 0000000..41583e3 --- /dev/null +++ b/gui/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/gui/bun.lock b/gui/bun.lock index d34d11c..ad96203 100644 --- a/gui/bun.lock +++ b/gui/bun.lock @@ -1,9 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "front", "dependencies": { + "@bradenmacdonald/s3-lite-client": "npm:@jsr/bradenmacdonald__s3-lite-client", + "@nostrify/nostrify": "npm:@jsr/nostrify__nostrify", "@tailwindcss/vite": "^4.1.14", "@tanstack/react-query": "^5.85.9", "any-ascii": "^0.3.3", @@ -73,6 +76,8 @@ "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + "@bradenmacdonald/s3-lite-client": ["@jsr/bradenmacdonald__s3-lite-client@0.9.5", "https://npm.jsr.io/~/11/@jsr/bradenmacdonald__s3-lite-client/0.9.5.tgz", {}, "sha512-zGopLx2evvA4cDezUYI5dMk1axF665FmFAgz1ShjP6YYx9KKT4GzpM8Nf6KY+f41z2lS1QdVUgwL9g6AH760Dw=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], @@ -163,6 +168,12 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@jsr/nostrify__types": ["@jsr/nostrify__types@0.36.0", "https://npm.jsr.io/~/11/@jsr/nostrify__types/0.36.0.tgz", {}, "sha512-kDjFf1nJMhZafULtH2GW+TWjs85J0aDxYYLpwBDQugDVsFqG2kLbxnoE2NtMJCIMNk1vUaipUHeil//4fY/d2w=="], + + "@jsr/scure__base": ["@jsr/scure__base@1.2.6", "https://npm.jsr.io/~/11/@jsr/scure__base/1.2.6.tgz", {}, "sha512-oZvJweJ4VMgVHJdsp8wwcG7t1U8aZLe0h8f4oNSFfavGqOKhpmi0b9mZ8vXLtGzBGiqVjK8Mon1NVMzTJincLQ=="], + + "@jsr/std__encoding": ["@jsr/std__encoding@0.224.3", "https://npm.jsr.io/~/11/@jsr/std__encoding/0.224.3.tgz", {}, "sha512-zAuX2QV1zwJ5RSmrnDGVerAtN3pBXpYYNlGzhERW9AiQ1UJd2/xruyB3i5NdTWy2OK2pjETswOj+0+prYTPlxQ=="], + "@noble/ciphers": ["@noble/ciphers@0.5.3", "", {}, "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="], "@noble/curves": ["@noble/curves@1.2.0", "", { "dependencies": { "@noble/hashes": "1.3.2" } }, "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw=="], @@ -175,6 +186,8 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@nostrify/nostrify": ["@jsr/nostrify__nostrify@0.46.5", "https://npm.jsr.io/~/11/@jsr/nostrify__nostrify/0.46.5.tgz", { "dependencies": { "@jsr/nostrify__types": "^0.36.0", "@jsr/scure__base": "^1.2.4", "@jsr/std__encoding": "^0.224.1", "lru-cache": "^10.2.0", "nostr-tools": "^2.13.0", "websocket-ts": "^2.2.1", "zod": "^3.23.8" } }, "sha512-l81N0o1A4WKAnOhq600ASDLxZbwzFZzUjhAkARPNFbWkXmEssVvXEQXyyoy1s7lboK45iZCijW0Bia7NHXmSug=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="], @@ -495,7 +508,7 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="], @@ -621,6 +634,8 @@ "vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], + "websocket-ts": ["websocket-ts@2.2.1", "", {}, "sha512-YKPDfxlK5qOheLZ2bTIiktZO1bpfGdNCPJmTEaPW7G9UXI1GKjDdeacOrsULUS000OPNxDVOyAuKLuIWPqWM0Q=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -635,8 +650,12 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], @@ -665,10 +684,10 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/gui/devenv.lock b/gui/devenv.lock index 19bac94..63e15ea 100644 --- a/gui/devenv.lock +++ b/gui/devenv.lock @@ -3,10 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1758064567, + "lastModified": 1770047045, "owner": "cachix", "repo": "devenv", - "rev": "bc697443a9653586e5be5150b4458f3096a93f67", + "rev": "5c0090a96b72ef4a1e1aec3e0abd30fc260062ac", "type": "github" }, "original": { @@ -19,14 +19,14 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, - "owner": "edolstra", + "lastModified": 1767039857, + "owner": "NixOS", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -40,10 +40,10 @@ ] }, "locked": { - "lastModified": 1757974173, + "lastModified": 1769939035, "owner": "cachix", "repo": "git-hooks.nix", - "rev": "302af509428169db34f268324162712d10559f74", + "rev": "a8ca480175326551d6c4121498316261cbb5b260", "type": "github" }, "original": { @@ -60,10 +60,10 @@ ] }, "locked": { - "lastModified": 1709087332, + "lastModified": 1762808025, "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", "type": "github" }, "original": { @@ -74,10 +74,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755783167, + "lastModified": 1767052823, "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "4a880fb247d24fbca57269af672e8f78935b0328", + "rev": "538a5124359f0b3d466e1160378c87887e3b51a4", "type": "github" }, "original": { diff --git a/gui/devenv.nix b/gui/devenv.nix index af7c11f..e4e3748 100644 --- a/gui/devenv.nix +++ b/gui/devenv.nix @@ -8,14 +8,6 @@ # https://devenv.sh/basics/ env.GREET = "devenv"; - env.ANTHROPIC_BASE_URL = "https://api.moonshot.ai/anthropic"; - env.ANTHROPIC_AUTH_TOKEN = "sk-el9tYLoqFmDrauY293aUpMUvgncoYjCtofRjKsdgrrI9NrP2"; - env.ANTHROPIC_MODEL = "kimi-k2-thinking-turbo"; - env.ANTHROPIC_DEFAULT_OPUS_MODEL = "kimi-k2-thinking-turbo"; - env.ANTHROPIC_DEFAULT_SONNET_MODEL = "kimi-k2-thinking-turbo"; - env.ANTHROPIC_DEFAULT_HAIKU_MODEL = "kimi-k2-thinking-turbo"; - env.CLAUDE_CODE_SUBAGENT_MODEL = "kimi-k2-thinking-turbo "; - # https://devenv.sh/packages/ packages = with pkgs; [ git diff --git a/gui/index.html b/gui/index.html index 94cc361..45456ee 100644 --- a/gui/index.html +++ b/gui/index.html @@ -5,6 +5,13 @@ + + + + +