summaryrefslogtreecommitdiff
path: root/desk/app/pals.hoon
diff options
context:
space:
mode:
Diffstat (limited to 'desk/app/pals.hoon')
-rw-r--r--desk/app/pals.hoon385
1 files changed, 385 insertions, 0 deletions
diff --git a/desk/app/pals.hoon b/desk/app/pals.hoon
new file mode 100644
index 0000000..5a50743
--- /dev/null
+++ b/desk/app/pals.hoon
@@ -0,0 +1,385 @@
+:: pals: manual peer discovery
+::
+:: acts as a "friendlist" of sorts, letting one add arbitrary ships to
+:: arbitrary lists. upon doing so, the other party is informed of this.
+:: this lets the app expose "friend requests" and mutuals, in addition
+:: to user-defined sets of friends.
+::
+:: intended use case is as simple, bare-bones peer discovery and
+:: permissioning for truly peer-to-peer applications, in place of
+:: (or as supplement to) group-based peer discovery.
+:: for example, a game wanting to stay abreast of high scores,
+:: or filesharing service giving selective access.
+::
+:: "leeches" are ships who added us.
+:: "targets" are ships we have added.
+:: "mutuals" is the intersection of "leeches" and "targets".
+::
+:: reading
+:: external applications likely want to read from this via scries or
+:: watches, both of which are outlined below.
+:: finding interaction targets or mutuals to poke or subscribe to, using
+:: mutual status as permission check, etc.
+:: to scry data out of this app, please use /lib/pals.
+:: one might be tempted to use list names for namespacing (ie %yourapp
+:: would only retrieve targets from the ~.yourapp list), but beware that
+:: this overlaps with user-facing organizational purposes. if lists feel
+:: opaque or inaccessible, it's to discourage this. but the right balance
+:: might not have been found yet...
+::
+:: writing
+:: poke this app with a $command.
+:: %meet adds a ship. it is also added to any list names specified.
+:: %part removes a ship from either all or the specified lists.
+:: the ~. list name is reserved and cannot be added to.
+:: managing pals without an interface that lets users control that behavior
+:: is bad manners. managing pals without informing the user is evil.
+::
+:: scry endpoints (all %noun marks)
+::NOTE %y at / doesn't actually work because gall eats it ):
+:: y / arch [%leeches %targets %mutuals ~]
+:: y /[status] arch non-empty lists listing
+::
+:: x / records full pals state
+:: x /leeches (set ship) foreign one-sided friendships
+:: x /targets(/[list]) (set ship) local one-sided friendships
+:: x /mutuals(/[list]) (set ship) mutual friendships
+::
+:: x /leeches/[ship] ? is ship a leeche?
+:: x /targets/[list]/[ship] ? is ship a target? list may be ~. for all
+:: x /mutuals/[list]/[ship] ? is ship a mutual? list may be ~. for all
+::
+:: subscription endpoints (local ship only, all %pals-effect marks)
+:: /targets target-effect effect for every addition/removal
+:: /leeches leeche-effect effect for every addition/removal
+::
+/- *pals
+/+ rudder, dbug, verb, default-agent
+::
+/~ pages (page:rudder records command) /app/pals/webui
+::
+|%
++$ state-1 [%1 records]
+::
++$ card card:agent:gall
+--
+::
+=| state-1
+=* state -
+::
+%- agent:dbug
+%+ verb |
+^- agent:gall
+::
+|_ =bowl:gall
++* this .
+ def ~(. (default-agent this %|) bowl)
+::
+++ on-init
+ ^- (quip card _this)
+ =^ cards this
+ (on-poke %pals-command !>(`command`[%meet ~paldev ~]))
+ :_ this
+ :+ [%pass /jael/pubs %arvo %j %public-keys ~]
+ [%pass /eyre/connect %arvo %e %connect [~ /[dap.bowl]] dap.bowl]
+ cards
+::
+++ on-save !>(state)
+::
+++ on-load
+ |= ole=vase
+ |^ ^- (quip card _this)
+ =/ old=state-n !<(state-n ole)
+ =^ caz=(list card) old
+ ?. ?=(%0 -.old) [~ old]
+ =. state [%1 +.old]
+ =^ caz this
+ ::TODO run this again some time in the future, to solve for
+ :: the "breached & never re-added you" case, where they
+ :: might not know you need to hear a %bye.
+ (on-poke %noun !>(%resend))
+ [[[%pass /jael/pubs %arvo %j %public-keys ~] caz] state]
+ ?> ?=(%1 -.old)
+ [caz this(state old)]
+ ::
+ +$ state-n $%(state-1 state-0)
+ +$ state-0 [%0 records]
+ --
+::
+++ on-poke
+ |= [=mark =vase]
+ ^- (quip card _this)
+ ?+ mark (on-poke:def mark vase)
+ %noun
+ ?+ q.vase $(mark %pals-command)
+ %resend
+ =/ out=(set ship) ~(key by outgoing)
+ =. receipts
+ =/ out=(list ship) ~(tap in out)
+ |-
+ ?~ out receipts
+ =. receipts (~(del by receipts) i.out)
+ $(out t.out)
+ :_ this
+ %+ weld
+ %+ turn ~(tap in out)
+ |= o=ship
+ [%pass /hey %agent [o dap.bowl] %poke %pals-gesture !>([%hey ~])]
+ %+ turn ~(tap in (~(dif in incoming) out))
+ |= i=ship
+ [%pass /bye %agent [i dap.bowl] %poke %pals-gesture !>([%bye ~])]
+ ==
+ ::
+ :: %pals-command: local app control
+ ::
+ %pals-command
+ ?> =(our src):bowl
+ =+ !<(cmd=command vase)
+ ?: (~(has in in.cmd) ~.)
+ ~| [%illegal-empty-list-name in=-.cmd]
+ !!
+ ?: =(our.bowl ship.cmd)
+ [~ this]
+ ::
+ =/ known=? (~(has by outgoing) ship.cmd)
+ =; [yow=? =_outgoing]
+ ^- (quip card _this)
+ =? receipts yow
+ :: if we're sending a new %hey, clear any existing receipt.
+ :: if we're sending a %bye, no need to track the old receipt.
+ ::
+ (~(del by receipts) ship.cmd)
+ :_ this(outgoing.state outgoing)
+ ?. yow ~
+ :~ =/ =gesture ?-(-.cmd %meet [%hey ~], %part [%bye ~])
+ =/ =cage [%pals-gesture !>(gesture)]
+ [%pass /[-.gesture] %agent [ship.cmd dap.bowl] %poke cage]
+ ::
+ =/ =effect ?-(-.cmd %meet [- ship]:cmd, %part [- ship]:cmd)
+ =/ =cage [%pals-effect !>(effect)]
+ [%give %fact [/targets]~ cage]
+ ==
+ ::
+ ?- -.cmd
+ %meet
+ :- !known
+ %+ ~(put by outgoing) ship.cmd
+ %- ~(uni in in.cmd)
+ (~(gut by outgoing) ship.cmd ~)
+ ::
+ %part
+ ?: =(~ in.cmd)
+ :: remove target entirely
+ ::
+ [known (~(del by outgoing) ship.cmd)]
+ :: remove from specified lists
+ ::
+ :- |
+ =. outgoing
+ =/ liz=(list @ta) ~(tap in in.cmd)
+ |- ^+ outgoing
+ ?~ liz outgoing
+ $(liz t.liz, outgoing (~(del ju outgoing) ship.cmd i.liz))
+ ::NOTE we could account for this above, but +del:ju is just easier there
+ =? outgoing !(~(has by outgoing) ship.cmd)
+ (~(put by outgoing) ship.cmd ~)
+ outgoing
+ ==
+ ::
+ :: %pals-gesture: foreign %pals signals
+ ::
+ %pals-gesture
+ ?< =(our src):bowl
+ =* ship src.bowl
+ =+ !<(=gesture vase)
+ =/ [yow=? =_incoming]
+ =* has (~(has in incoming) ship)
+ ?- -.gesture
+ %hey :- !has (~(put in incoming) ship)
+ %bye :- has (~(del in incoming) ship)
+ ==
+ :_ this(incoming.state incoming)
+ ^- (list card)
+ ?. yow ~
+ :* =/ =effect ?-(-.gesture %hey [%near ship], %bye [%away ship])
+ =/ =cage [%pals-effect !>(effect)]
+ [%give %fact [/leeches]~ cage]
+ ::
+ ?. .^(? %gu /(scot %p our.bowl)/hark/(scot %da now.bowl)/$) ~
+ =/ body
+ =- [ship+ship - ~]
+ ?- -.gesture
+ %hey ' added you as a pal.'
+ %bye ' no longer considers you a pal.'
+ ==
+ =/ id (end 7 (shas %pals-notification eny.bowl))
+ =/ rope [~ ~ q.byk.bowl /(scot %p ship)/[-.gesture]]
+ =/ action [%add-yarn & & id rope now.bowl body /pals ~]
+ =/ =cage [%hark-action !>(action)]
+ [%pass /hark %agent [our.bowl %hark] %poke cage]~
+ ==
+ ::
+ :: %handle-http-request: incoming from eyre
+ ::
+ %handle-http-request
+ =; out=(quip card _+.state)
+ [-.out this(+.state +.out)]
+ %. [bowl !<(order:rudder vase) +.state]
+ %- (steer:rudder _+.state command)
+ :^ pages
+ (point:rudder /[dap.bowl] & ~(key by pages))
+ (fours:rudder +.state)
+ |= cmd=command
+ ^- $@ brief:rudder
+ [brief:rudder (list card) _+.state]
+ =^ caz this
+ (on-poke %pals-command !>(cmd))
+ ['Processed succesfully.' caz +.state]
+ ==
+::
+++ on-watch
+ |= =path
+ ^- (quip card _this)
+ ?> =(our.bowl src.bowl)
+ ?+ path (on-watch:def path)
+ [%http-response *] [~ this]
+ ::
+ [%targets ~]
+ :_ this
+ %+ turn ~(tap in ~(key by outgoing))
+ |=(=@p [%give %fact ~ %pals-effect !>(`effect`[%meet p])])
+ ::
+ [%leeches ~]
+ :_ this
+ %+ turn ~(tap in incoming)
+ |=(=@p [%give %fact ~ %pals-effect !>(`effect`[%near p])])
+ ::
+ ::TODO consider adding a subscription endpoint that includes tags?
+ :: shouldn't become too legible to applications though...
+ ==
+::
+++ on-agent
+ |= [=wire =sign:agent:gall]
+ ^- (quip card _this)
+ ?+ wire ~&([dap.bowl %strange-wire wire] [~ this])
+ [%hark ~]
+ ?. ?=(%poke-ack -.sign) (on-agent:def wire sign)
+ ?~ p.sign [~ this]
+ ((slog 'pals: failed to notify' u.p.sign) [~ this])
+ ::
+ [%bye ~] [~ this] ::TODO also retry if nack?
+ [%hey ~]
+ :: for %pals-gesture pokes, record the result
+ ::TODO should we slowly retry for nacks?
+ ::
+ =- [~ this(receipts -)]
+ ?+ -.sign ~|([%unexpected-agent-sign wire -.sign] !!)
+ %poke-ack (~(put by receipts) src.bowl ?=(~ p.sign))
+ ==
+ ==
+::
+++ on-arvo
+ |= [=wire =sign-arvo]
+ ^- (quip card _this)
+ ?+ wire ~|([dap.bowl %strange-wire wire] !!)
+ [%eyre %connect ~]
+ ?. ?=([%eyre %bound *] sign-arvo)
+ (on-arvo:def wire sign-arvo)
+ ~? !accepted.sign-arvo
+ [dap.bowl 'eyre bind rejected!' binding.sign-arvo]
+ [~ this]
+ ::
+ [%jael %pubs ~]
+ ?. ?=([%jael %public-keys *] sign-arvo)
+ (on-arvo:def wire sign-arvo)
+ =/ who=(unit ship)
+ =* pkr public-keys-result.sign-arvo
+ ?+ -.public-keys-result.sign-arvo ~
+ %breach `who.pkr
+ ==
+ ?~ who [~ this]
+ :_ %_ this
+ incoming (~(del in incoming) u.who)
+ receipts (~(del by receipts) u.who)
+ ==
+ =; caz=(list (unit card))
+ (murn caz same)
+ :~ :: if they liked us, for now that's no longer true
+ ::
+ ?. (~(has in incoming) u.who) ~
+ =/ =cage [%pals-effect !>(`effect`[%away u.who])]
+ `[%give %fact [/leeches]~ cage]
+ ::
+ :: if we liked them, assume they come back and remind them
+ ::
+ ?. (~(has by outgoing) u.who) ~
+ =/ =cage [%pals-gesture !>(`gesture`[%hey ~])]
+ `[%pass /hey %agent [u.who dap.bowl] %poke cage]
+ ==
+ ==
+::
+++ on-peek
+ |= =path
+ ^- (unit (unit cage))
+ ?> =(our src):bowl
+ |^ ?+ path [~ ~]
+ [%x ~] ``noun+!>(`records`+.state)
+ [%y ~] (arc %leeches %targets %mutuals ~)
+ [%y %leeches ~] (arc ~)
+ [%y %targets ~] (arc (las targets))
+ [%y %mutuals ~] (arc (las mutuals))
+ [%x %leeches ~] (alp leeches)
+ [%x %leeches @ ~] (ask (bind (slaw %p i.t.t.path) (sin leeches)))
+ [%x %targets ~] (alp targets)
+ [%x %targets ~ ~] [~ ~]
+ [%x %targets @ta ~] (alp (lap targets i.t.t.path))
+ [%x %targets @ta @ ~] (ask (bind (wat t.t.path) (hal targets)))
+ [%x %mutuals ~] (alp mutuals)
+ [%x %mutuals ~ ~] [~ ~]
+ [%x %mutuals @ta ~] (alp (lap mutuals i.t.t.path))
+ [%x %mutuals @ta @ ~] (ask (bind (wat t.t.path) (hal mutuals)))
+ ::
+ [%x %json ~] ::NOTE dumb hack, subject to change
+ =; =json ``json+!>(json)
+ =, enjs:format
+ %- pairs
+ :~ :- 'outgoing'
+ %- pairs
+ %+ turn ~(tap by outgoing)
+ |= [=^ship lists=(set @ta)]
+ :- (rsh 3 (scot %p ship))
+ %- pairs
+ :~ 'lists'^a+(turn ~(tap in lists) (lead %s))
+ 'ack'^(fall (bind (~(get by receipts) ship) (lead %b)) ~)
+ ==
+ ::
+ :- 'incoming'
+ %- pairs
+ %+ turn ~(tap in incoming)
+ |=(=^^ship [(rsh 3 (scot %p ship)) b+&])
+ ==
+ ==
+ :: scry results
+ ++ arc |= l=(list @ta) ``noun+!>(`arch`~^(malt (turn l (late ~))))
+ ++ alp |= s=(set @p) ``noun+!>(s)
+ ++ alf |= f=? ``noun+!>(f)
+ ++ ask |= u=(unit ?) ?^(u (alf u.u) [~ ~])
+ :: data wrestling
+ ++ wat |=([l=@ta p=@ta ~] ?~(p=(slaw %p p) ~ (some [l u.p])))
+ ++ nab ~(got by outgoing)
+ ++ las |=(s=(set @p) (zing (turn (sit s) |=(=@p (sit (nab p))))))
+ ++ lap |=([s=(set @p) l=@ta] (ski s |=(=@p ((sin (nab p)) l))))
+ ++ hal |=(s=(set @p) |=([l=@ta =@p] ((sin ?~(l s (lap s l))) p)))
+ :: set shorthands
+ ++ sin |*(s=(set) ~(has in s))
+ ++ sit |*(s=(set) ~(tap in s))
+ ++ ski |*([s=(set) f=$-(* ?)] (sy (skim (sit s) f)))
+ :: pals
+ ++ leeches incoming
+ ++ targets ~(key by outgoing)
+ ++ mutuals (~(int in targets) leeches)
+ --
+::
+++ on-leave on-leave:def
+++ on-fail on-fail:def
+--