This commit is contained in:
polwex 2024-10-06 18:19:22 +00:00
commit d5cc07c259
232 changed files with 92701 additions and 0 deletions

3
.envrc Normal file
View File

@ -0,0 +1,3 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
use devenv

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml

10
LICENSE Normal file
View File

@ -0,0 +1,10 @@
MIT License
Copyright (c) 2024 polwex
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Urbit Community Manager
Create self-hosted communities on Urbit.
Currently supports Blog, Static sites, Chat, Forum, Radio and Wiki apps.
Supports EAuth and Metamask login with Urbit ID.
### Installation
Two options:
1.- On dojo run `|install ~dister-dozzod-sortug %ucm`
2.- Symlink the `desk` folder to a folder inside your pier, then commit and `|install our <name>`.

329
desk/app/ucm.hoon Normal file
View File

@ -0,0 +1,329 @@
/- sur=ucm, blog-paths, docket
:: XX sss; remove on transition to %4
/+ jon=json, lib=ucm, dbug, *sss, cons=constants, metamask, sr=sortug, apps, web
:: /= router /web/router
::
%- agent:dbug
^- agent:gall
=> |%
+$ versioned-state
$% state-0:sur
==
:: XX move into +* below
+$ card $+(card card:agent:gall)
--
=| versioned-state
=* state -
::
=<
|_ =bowl:gall
+* this .
hd ~(. +> [state bowl])
:: rout ~(. router:router [state bowl])
metalib ~(. metamask [sessions.state bowl])
:: XX sss; remove on transition to state-4
du-paths
=/ du
(du blog-paths ,[%paths ~])
(du pub-paths bowl -:!>(*result:du))
::
++ on-init
^- (quip card _this)
:_ this
:_ ~ (add-eyre-binding [dap.bowl ~])
::
++ on-save
!>(state)
::
++ on-load
|= =vase
^- (quip card _this)
=+ !<(old=versioned-state vase)
?- -.old
%0 :_ this(state old) ~
==
::
++ on-poke
|= [=mark =vase]
=. src.bowl login:metalib
|^
^- (quip card _this)
?+ mark
~|("unexpected poke to {<dap.bowl>} with mark {<mark>}" !!)
::
%noun (handle-poke !<(* vase))
%json (handle-json !<(json vase))
%handle-http-request (serve-http !<([@ta inbound-request:eyre] vase))
== :: end of pokes
++ handle-poke |= a=*
?: ?=([%ui *] a) (handle-ui a)
:: metamask
?: ?=([%meta @] a)
=. sessions (handle-meta:metalib +.a) `this
?: ?=([%auth @p @p @uv] a)
=. sessions (handle-auth:metalib +.a) `this
?: ?=(%sessions a) handle-sess
?: ?=(%chal a) handle-chal
?: ?=([%glob *] a) (handle-glob +.a)
(handle-debug a)
::
++ handle-json |= j=json
=/ =action:sur (de:jon j)
?- -.action
%dash (handle-dash +.action)
%site (handle-site +.action)
==
++ handle-dash |= a=dash-action:sur
?- -.a
%del =. sites (~(del by sites) name.a)
`this
%set
=/ site (~(get by sites) binding.a)
=/ nsite ?~ site *site:sur u.site
=/ ns %= nsite
sitename sitename.a
description description.a
groupname groupname.a
binding binding.a
icon icon.a
home home.a
css css.a
hidden hidden.a
apps apps.a
app-order app-order.a
==
=. sites (~(put by sites) binding.a ns)
:: =/ cards [(add-eyre-binding:hd binding.a) (diff-sites nsite ns)]
=/ cards [(add-eyre-binding:hd binding.a) ~]
[cards this]
==
++ handle-site |= [name=@t a=site-action:sur]
?- -.a
%lol `this
%search `this
==
:: ++ diff-sites |= [sa=site:sur sb=site:sur] ^- (list card)
:: =/ applib ~(. apps [groupname.sb bowl])
:: =, applib
:: =/ a apps.sa =/ b apps.sb
:: %- zing
:: :~ (group-diff sa sb)
:: (blog-diff blog.a blog.b)
:: (chat-diff chat.a chat.b)
:: (forum-diff forum.a forum.b)
:: (radio-diff radio.a radio.b)
:: (wiki-diff wiki.a wiki.b)
:: ==
++ handle-sess
~& >> users.sessions
`this
++ handle-chal
~& >> challenges.sessions
~& "wiping challenges"
=. challenges.sessions *(set @uv)
`this
++ handle-glob |= arg=*
=/ pat (path arg)
=/ ta-now `@ta`(scot %da now.bowl)
:_ this :_ ~
[%pass (weld /glob pat) %arvo %k %fard dap.bowl %make-glob %noun !>(pat)]
::
++ handle-debug |= arg=*
~& debug=arg
=/ applib ~(. apps [%lol bowl])
?: .=(%seed arg)
`this
?: .=(%print arg)
=/ log
=/ l ~(tap by sites)
|- ?~ l ~
=/ site +.i.l
~& site
$(l t.l)
`this
?: .=(%vats arg) ~& show-vats:applib `this
?: .=(%eyre arg) :_ this
=/ cards wipe-eyre-bindings:hd
~& cards=cards
cards
?: .=([%add *] arg)
=/ ssite *site:sur
=/ ms %= ssite
sitename 'My test site'
binding /test1
groupname %my-test-site-3
==
=. sites (~(put by sites) /test1 ms)
`this
?: ?=([%order *] arg)
~& > ordering=arg
=/ pat (path +.arg)
~& > ordering=pat
=/ site (~(get by sites) pat)
~& site
?~ site `this
:: TODO resurrect when wiki is usable
:: =. app-order.u.site ~[%blog %chat %forum %radio %wiki]
=. app-order.u.site ~[%blog %chat %forum %radio]
=. sites (~(put by sites) pat u.site)
`this
?: .=(%wipe-subs arg)
:_ this %+ weld
%+ turn ~(tap by wex.bowl) |= [[=wire =ship =term] [acked=? =path]]
^- card
[%pass wire %agent [ship term] %leave ~]
%+ turn ~(tap by sup.bowl) |= [* p=@p pat=path]
^- card
[%give %kick ~[pat] `p]
:: ?: .=(arg 5)
:: :_ this :_ ~ (add-eyre-binding /test)
:: ?: .=(arg 4)
:: :_ this :_ ~ (remove-eyre-binding /test)
:: ~& >> unrecognized-poke-to-ublog=a
`this
:: UI poke handler
++ handle-ui |= a=*
:: =/ ua ((soft sail-poke:sur) a)
:: ?~ ua `this =/ action action.u.ua
:: =. sites
:: ?+ -.action sites
:: %launch (handle-launch +.action)
:: %delist (handle-delist +.action)
:: %delete (handle-delete +.action)
:: :: %add-app
:: :: %hide-app
:: :: %del-app
:: ==
:: :_ this (redirect:router eyre-id.u.ua "")
`this
:: ++ handle-launch |= [name=@t]
:: ?: =('' name) sites
:: =/ site (~(get by sites) name)
:: ?~ site
:: =/ nsite *site:sur
:: (~(put by sites) name nsite)
:: =. hidden.u.site .n
:: (~(put by sites) name u.site)
:: ++ handle-delist |= name=@t
:: =/ site (~(get by sites) name)
:: ?~ site sites
:: =. hidden.u.site .y
:: (~(put by sites) name u.site)
:: ++ handle-delete |= name=@t
:: (~(del by sites) name)
++ serve-http |= [id=@ta req=inbound-request:eyre]
=/ weblib ~(. web [bowl id req state])
:- route:weblib this
--
::
++ on-peek
|= =path
^- (unit (unit cage))
:: ~& >> "attempting scry on {<path>}"
?+ path ~&("unexpected scry into {<dap.bowl>} on path {<path>}" ~)
[%x %state ~] :- ~ :- ~ :- %json !>
(en:jon sites)
==
++ on-watch
|= =(pole knot)
?+ pole !!
:: dashboard
[%ui ~] :_ this :~(give-state:hd)
:: site
[%ui rest=*]
:_ this :~((give-site-state:hd rest.pole))
[%http-response *] `this
[%proxy app=@t rest=*]
:_ this
:: ~
:_ ~
(proxy-watch:hd rest.pole our.bowl app.pole)
==
::
++ on-arvo
|= [=(pole knot) =sign-arvo]
`this
++ on-agent |= [wire=(pole knot) =sign:agent:gall]
:: ?: ?=(%kick -.sign) :_ this :~((re-watch:hd wire))
?. ?=(%fact -.sign) `this
?+ wire `this
[%ninja-sub app=@t rest=*]
=/ return-path (weld /proxy [app.wire rest.wire])
=/ =mars:clay [p.cage.sign %json]
=/ mars-path /[a.mars]/[b.mars]
:: this is annoyingly ad-hoc but I'm tired
=/ base ?: ?=(?(%tower %tenna) app.wire)
/(scot %p our.bowl)/[%radio]/(scot %da now.bowl)
/(scot %p our.bowl)/[%groups]/(scot %da now.bowl)
=/ scry-path (weld base mars-path)
=/ mark-conversion-gate .^(tube:clay %cc scry-path)
=/ vas (mark-conversion-gate q.cage.sign)
:_ this :_ ~
[%give %fact ~[return-path] %json vas]
==
++ on-leave |~(* [~ this])
++ on-fail |~(* [~ this])
--
|_ [s=versioned-state =bowl:gall]
:: cards
++ add-eyre-binding
|= =path ^- card
[%pass /eyre/connect %arvo %e %connect [~ path] dap.bowl]
++ wipe-eyre-bindings
=/ l .^((list [=binding:eyre duct =action:eyre]) %e /(scot %p our.bowl)/bindings/(scot %da now.bowl))
=/ cards=(list card) [(add-eyre-binding [dap.bowl ~]) ~]
|- ?~ l cards
=/ target action.i.l
?. ?=(%app -.target) $(l t.l)
?. .=(dap.bowl app.target) $(l t.l)
=/ nc (remove-eyre-binding binding.i.l)
=. cards [nc cards]
$(l t.l)
++ remove-eyre-binding |= =binding:eyre ^- card
[%pass /eyre/disconnect %arvo %e %disconnect binding]
++ give-state ^- card
[%give %fact ~ [%json !>((en:jon sites))]]
++ give-site-state |= sitepath=path ^- card
=/ usite (~(get by sites) sitepath)
=/ j=json
?~ usite ~ (en-site:en:jon u.usite)
[%give %fact ~ [%json !>(j)]]
++ proxy-watch |= [=path =ship app=term] ^- card
=/ sub-path (weld /ninja-sub [app path])
[%pass sub-path %agent [ship app] %watch path]
++ re-watch |= wire=(pole knot) ^- card
?. ?=([%ninja-sub app=@t rest=*] wire) !!
[%pass wire %agent [our.bowl app.wire] %watch rest.wire]
:: ++ cache-card
:: |= path=tape ^- card
:: =/ pathc (crip "{base-url:cons}{path}")
:: ~& >> caching=pathc
:: =/ router-path ?~ path '/' pathc
:: =/ pl=simple-payload:http (render:rout router-path)
:: =/ entry=cache-entry:eyre [.n %payload pl]
:: [%pass /root %arvo %e %set-response pathc `entry]
:: ++ uncache-card
:: |= path=tape ^- card
:: =/ pathc (crip "{base-url:cons}{path}")
:: ~& >> uncaching=pathc
:: =/ router-path ?~ path '/' pathc
:: =/ pl=simple-payload:http (render:rout router-path)
:: =/ entry=cache-entry:eyre [.n %payload pl]
:: [%pass /root %arvo %e %set-response pathc ~]
--

2
desk/desk.bill Normal file
View File

@ -0,0 +1,2 @@
:~ %ucm
==

11
desk/desk.docket-0 Normal file
View File

@ -0,0 +1,11 @@
:~
title+'Urbit Community Manager'
info+'Self-host your whole community on Urbit'
color+0xef.f0f4
version+[0 1 0]
website+'https://sortug.com'
license+'MIT'
image+'https://s3.sortug.com/img/icons/ucm-logo.png'
base+'ucm'
glob-http+['https://s3.sortug.com/globs/glob-0v1.fiva2.d83ct.k90tn.n7tjl.58mjq.glob' 0v1.fiva2.d83ct.k90tn.n7tjl.58mjq]
==

1
desk/desk.ship Normal file
View File

@ -0,0 +1 @@
~dister-bonbud-macryg

View File

@ -0,0 +1,5 @@
:- %say
:: css is whether to append the css to exported html in <style> tags
|= [[*] [css=? ~] ~]
:- %blog-action
[%export css]

View File

@ -0,0 +1 @@
#root{max-width:1280px;margin:0 auto;padding:2rem;text-align:center}.logo{height:6em;padding:1.5em;will-change:filter;transition:filter .3s}.logo:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.react:hover{filter:drop-shadow(0 0 2em #61dafbaa)}@keyframes logo-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (prefers-reduced-motion: no-preference){a:nth-of-type(2) .logo{animation:logo-spin infinite 20s linear}}.card{padding:2em}.read-the-docs{color:#888}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/test1/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ublog</title>
<script type="module" crossorigin src="/test1/assets/a4e0f2ed22786f63-index.js"></script>
<link rel="stylesheet" crossorigin href="/test1/assets/7e77ee776b761225-index.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

147
desk/lib/apps.hoon Normal file
View File

@ -0,0 +1,147 @@
/- hood, sur=ucm, tg=tlon-groups
/+ sr=sortug
|_ [group-name=term =bowl:gall]
+$ card card:agent:gall
++ blog-diff |= [a=(unit @t) b=(unit @t)] ^- (list card)
?~ a ?~ b ~
:: :: no a, yes b
:~((create-chan %diary u.b))
:: :: yes a
?~ b :~((del-chan %diary u.a))
:~((change-diary-name u.a u.b))
++ chat-diff |= [a=(set term) b=(set term)]
=/ adif (~(dif in a) b) :: set of elements in a but not in b
=/ bdif (~(dif in b) a)
%+ weld (del-chats adif) (new-chats bdif)
++ forum-diff |= [a=(set term) b=(set term)]
=/ adif (~(dif in a) b) :: set of elements in a but not in b
=/ bdif (~(dif in b) a)
%+ weld (del-diaries adif) (create-diaries bdif)
++ radio-diff |= [a=(set term) b=(set term)]
=/ adif (~(dif in a) b) :: set of elements in a but not in b
=/ bdif (~(dif in b) a)
%+ weld (del-radios adif) (new-radios bdif)
++ wiki-diff |= [a=(set term) b=(set term)]
=/ adif (~(dif in a) b) :: set of elements in a but not in b
=/ bdif (~(dif in b) a)
%+ weld (del-wikis adif) (new-wikis bdif)
::
:: chat
++ new-chats |= a=(set @t)
%+ turn ~(tap in a)
|= t=@t (create-chan %chat t)
++ del-chats |= a=(set @t)
%+ turn ~(tap in a)
|= t=@t (del-chan %chat t)
:: blog
++ create-diaries |= a=(set @t)
%+ turn ~(tap in a)
|= t=@t (create-chan %diary t)
++ del-diaries |= a=(set @t)
%+ turn ~(tap in a)
|= t=@t (del-chan %diary t)
++ create-chan |= [=kind:tg title=@t] ^- card
=/ name (enkebab:string:sr title)
=/ desc 'TODO'
=/ cage :- %channel-command !>
:* %create
kind=kind
name=name
group=[p=our.bowl q=group-name]
title=title
description=desc
readers=~
writers=~
==
(poke-card %channels-server cage)
++ del-chan |= [=kind:tg t=@t] ^- card
=/ name (enkebab:string:sr t)
=/ nest [kind our.bowl name]
=/ diff [%channel nest %del ~]
=/ =cage :- %group-action-3 !>
:* [our.bowl group-name]
now.bowl
diff
==
(poke-card %groups cage)
++ change-diary-name |= [a=term b=term] ^- card
:: scry the former and add changes
:: =/ name (enkebab:string:sr title)
:: =/ nest [%diary our.bowl name]
:: =/ chan [meta added zone join readers]
:: =/ diff [%channel nest %edit chan]
=/ cage :- %group-action-3 !>
:* [our.bowl group-name]
now.bowl
:: diff
==
(poke-card %groups cage)
:: forum
::
++ group-diff |= [a=site:sur b=site:sur] ^- (list card)
?: .=(a *site:sur) :_ ~ (create-group sitename.b)
~
++ create-group |= title=@t ^- card
=/ name (enkebab:string:sr title)
=/ cage :- %group-create !>
:* name=name
title=title
description=''
image='#999999'
cover='#D9D9D9'
cordon=[%afar [our.bowl dap.bowl] /lol 'idk']
members=~
secret=%.n
==
(poke-card %groups cage)
:: radio
++ new-radios |= a=(set @t)
~
++ del-radios |= a=(set @t)
~
++ new-wikis |= a=(set @t)
~
++ del-wikis |= a=(set @t)
~
++ show-vats
=, clay
=/ ego (scot %p our.bowl)
=/ wen (scot %da now.bowl)
=/ desks .^((set desk) %cd /[ego]//[wen])
=/ res (report-prep:hood our.bowl now.bowl)
=/ rock -.res :: desks and their status
:: =/ cone +<.res
:: =/ dmp +>-.res
:: =/ idk +>+.res
:: ~& desks=desks
:: ~& rock=rock
:: ~& cone=cone
:: ~& deskmap=dmp
:: ~& idk=idk
%ok
++ install-card
|= [sip=@p app=term]
=/ =cage [%kiln-install !>([app sip app])]
[%pass /poke %agent [our.bowl %hood] %poke cage]
++ watch-card
|= [=wire app=term] ^- card
[%pass wire %agent [our.bowl app] %watch wire]
:: ++ leave-card ^- card
:: |= [=wire app=term] ^- card
:: [%pass wire %agent [our.bowl app] %leave ~]
++ poke-card
|= [app=term =cage] ^- card
[%pass /poke %agent [our.bowl app] %poke cage]
++ ui-fact |= [wires=(list path) =cage]
[%give %fact wires cage]
++ timer-card |= =wire
=/ timer
%+ add ~s1 -:(rads:~(. og eny.bowl) ~s4)
[%pass wire %arvo %b %wait (add now.bowl timer)]
--

6
desk/lib/constants.hoon Normal file
View File

@ -0,0 +1,6 @@
|%
++ app-name %ucm
++ base-url "/ucm"
++ magic-comet ^- @p
~polwex-polwex-dozzod-polwex--dozzod-polwex-dozzod-polwex
--

155
desk/lib/dbug.hoon Normal file
View File

@ -0,0 +1,155 @@
:: dbug: agent wrapper for generic debugging tools
::
:: usage: %-(agent:dbug your-agent)
::
|%
+$ poke
$% [%bowl ~]
[%state grab=cord]
[%incoming =about]
[%outgoing =about]
==
::
+$ about
$@ ~
$% [%ship =ship]
[%path =path]
[%wire =wire]
[%term =term]
==
::
++ agent
|= =agent:gall
^- agent:gall
!.
|_ =bowl:gall
+* this .
ag ~(. agent bowl)
::
++ on-poke
|= [=mark =vase]
^- (quip card:agent:gall agent:gall)
?. ?=(%dbug mark)
=^ cards agent (on-poke:ag mark vase)
[cards this]
=/ dbug
!<(poke vase)
=; =tang
((%*(. slog pri 1) tang) [~ this])
?- -.dbug
%bowl [(sell !>(bowl))]~
::
%state
=? grab.dbug =('' grab.dbug) '-'
=; product=^vase
[(sell product)]~
=/ state=^vase
:: if the underlying app has implemented a /dbug/state scry endpoint,
:: use that vase in place of +on-save's.
::
=/ result=(each ^vase tang)
(mule |.(q:(need (need (on-peek:ag /x/dbug/state)))))
?:(?=(%& -.result) p.result on-save:ag)
%+ slap
(slop state !>([bowl=bowl ..zuse]))
(ream grab.dbug)
::
%incoming
=; =tang
?^ tang tang
[%leaf "no matching subscriptions"]~
%+ murn
%+ sort ~(tap by sup.bowl)
|= [[* a=[=ship =path]] [* b=[=ship =path]]]
(aor [path ship]:a [path ship]:b)
|= [=duct [=ship =path]]
^- (unit tank)
=; relevant=?
?. relevant ~
`>[path=path from=ship duct=duct]<
?: ?=(~ about.dbug) &
?- -.about.dbug
%ship =(ship ship.about.dbug)
%path ?=(^ (find path.about.dbug path))
%wire %+ lien duct
|=(=wire ?=(^ (find wire.about.dbug wire)))
%term !!
==
::
%outgoing
=; =tang
?^ tang tang
[%leaf "no matching subscriptions"]~
%+ murn
%+ sort ~(tap by wex.bowl)
|= [[[a=wire *] *] [[b=wire *] *]]
(aor a b)
|= [[=wire =ship =term] [acked=? =path]]
^- (unit tank)
=; relevant=?
?. relevant ~
`>[wire=wire agnt=[ship term] path=path ackd=acked]<
?: ?=(~ about.dbug) &
?- -.about.dbug
%ship =(ship ship.about.dbug)
%path ?=(^ (find path.about.dbug path))
%wire ?=(^ (find wire.about.dbug wire))
%term =(term term.about.dbug)
==
==
::
++ on-peek
|= =path
^- (unit (unit cage))
?. ?=([@ %dbug *] path)
(on-peek:ag path)
?+ path [~ ~]
[%u %dbug ~] ``noun+!>(&)
[%x %dbug %state ~] ``noun+!>(on-save:ag)
[%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)
==
::
++ on-init
^- (quip card:agent:gall agent:gall)
=^ cards agent on-init:ag
[cards this]
::
++ on-save on-save:ag
::
++ on-load
|= old-state=vase
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-load:ag old-state)
[cards this]
::
++ on-watch
|= =path
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-watch:ag path)
[cards this]
::
++ on-leave
|= =path
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-leave:ag path)
[cards this]
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-agent:ag wire sign)
[cards this]
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-arvo:ag wire sign-arvo)
[cards this]
::
++ on-fail
|= [=term =tang]
^- (quip card:agent:gall agent:gall)
=^ cards agent (on-fail:ag term tang)
[cards this]
--
--

223
desk/lib/docket.hoon Normal file
View File

@ -0,0 +1,223 @@
/- *docket
|%
::
++ mime
|%
+$ draft
$: title=(unit @t)
info=(unit @t)
color=(unit @ux)
glob-http=(unit [=url hash=@uvH])
glob-ames=(unit [=ship hash=@uvH])
base=(unit term)
site=(unit path)
image=(unit url)
version=(unit version)
website=(unit url)
license=(unit cord)
==
::
++ finalize
|= =draft
^- (unit docket)
?~ title.draft ~
?~ info.draft ~
?~ color.draft ~
?~ version.draft ~
?~ website.draft ~
?~ license.draft ~
=/ href=(unit href)
?^ site.draft `[%site u.site.draft]
?~ base.draft ~
?^ glob-http.draft
`[%glob u.base hash.u.glob-http %http url.u.glob-http]:draft
?~ glob-ames.draft
~
`[%glob u.base hash.u.glob-ames %ames ship.u.glob-ames]:draft
?~ href ~
=, draft
:- ~
:* %1
u.title
u.info
u.color
u.href
image
u.version
u.website
u.license
==
::
++ from-clauses
=| =draft
|= cls=(list clause)
^- (unit docket)
=* loop $
?~ cls (finalize draft)
=* clause i.cls
=. draft
?- -.clause
%title draft(title `title.clause)
%info draft(info `info.clause)
%color draft(color `color.clause)
%glob-http draft(glob-http `[url hash]:clause)
%glob-ames draft(glob-ames `[ship hash]:clause)
%base draft(base `base.clause)
%site draft(site `path.clause)
%image draft(image `url.clause)
%version draft(version `version.clause)
%website draft(website `website.clause)
%license draft(license `license.clause)
==
loop(cls t.cls)
::
++ to-clauses
|= d=docket
^- (list clause)
%- zing
:~ :~ title+title.d
info+info.d
color+color.d
version+version.d
website+website.d
license+license.d
==
?~ image.d ~ ~[image+u.image.d]
?: ?=(%site -.href.d) ~[site+path.href.d]
=/ ref=glob-reference glob-reference.href.d
:~ base+base.href.d
?- -.location.ref
%http [%glob-http url.location.ref hash.ref]
%ames [%glob-ames ship.location.ref hash.ref]
== == ==
::
++ spit-clause
|= =clause
^- tape
%+ weld " {(trip -.clause)}+"
?+ -.clause "'{(trip +.clause)}'"
%color (scow %ux color.clause)
%site (spud path.clause)
::
%glob-http
"['{(trip url.clause)}' {(scow %uv hash.clause)}]"
::
%glob-ames
"[{(scow %p ship.clause)} {(scow %uv hash.clause)}]"
::
%version
=, version.clause
"[{(scow %ud major)} {(scow %ud minor)} {(scow %ud patch)}]"
==
::
++ spit-docket
|= dock=docket
^- tape
;: welp
":~\0a"
`tape`(zing (join "\0a" (turn (to-clauses dock) spit-clause)))
"\0a=="
==
--
::
++ enjs
=, enjs:format
|%
::
++ charge-update
|= u=^charge-update
^- json
%+ frond -.u
^- json
?- -.u
%del-charge s+desk.u
::
%initial
%- pairs
%+ turn ~(tap by initial.u)
|=([=desk c=^charge] [desk (charge c)])
::
%add-charge
%- pairs
:~ desk+s+desk.u
charge+(charge charge.u)
==
==
::
++ num
|= a=@u
^- ^tape
=/ p=json (numb a)
?> ?=(%n -.p)
(trip p.p)
::
++ version
|= v=^version
^- json
:- %s
%- crip
"{(num major.v)}.{(num minor.v)}.{(num patch.v)}"
::
++ merge
|= [a=json b=json]
^- json
?> &(?=(%o -.a) ?=(%o -.b))
[%o (~(uni by p.a) p.b)]
::
++ href
|= h=^href
%+ frond -.h
?- -.h
%site s+(spat path.h)
%glob
%- pairs
:~ base+s+base.h
glob-reference+(glob-reference glob-reference.h)
==
==
::
++ glob-reference
|= ref=^glob-reference
%- pairs
:~ hash+s+(scot %uv hash.ref)
location+(glob-location location.ref)
==
::
++ glob-location
|= loc=^glob-location
^- json
%+ frond -.loc
?- -.loc
%http s+url.loc
%ames s+(scot %p ship.loc)
==
::
++ charge
|= c=^charge
%+ merge (docket docket.c)
%- pairs
:~ chad+(chad chad.c)
==
::
++ docket
|= d=^docket
^- json
%- pairs
:~ title+s+title.d
info+s+info.d
color+s+(scot %ux color.d)
href+(href href.d)
image+?~(image.d ~ s+u.image.d)
version+(version version.d)
license+s+license.d
website+s+website.d
==
::
++ chad
|= c=^chad
%+ frond -.c
?+ -.c ~
%hung s+err.c
==
--
--

1006
desk/lib/ethereum.hoon Normal file

File diff suppressed because it is too large Load Diff

105
desk/lib/json.hoon Normal file
View File

@ -0,0 +1,105 @@
/- sur=ucm, tg=tlon-groups
|%
++ en
=, enjs:format
|_ =sites:sur
++ $ ^- json
%- pairs %+ turn ~(tap by sites) |= [pat=^path =site:sur]
=/ key (path pat)
?> ?=(%s -.key)
:- p.key (en-site site)
++ en-site |= =site:sur ^- json
%- pairs
:~ sitename+s+sitename.site
description+s+description.site
groupname+s+groupname.site
binding+(path binding.site)
icon+s+icon.site
home+s+home.site
css+s+css.site
apps+(en-apps apps.site)
app-order+a+(turn app-order.site en-navbar)
hidden+b+hidden.site
==
++ en-apps |= =apps:sur ^- json
%- pairs
:~ :- %blog [%s blog.apps]
:- %radio [%b radio.apps]
:+ %chat %a (turn ~(tap in chat.apps) enso)
:+ %forum %a (turn ~(tap in forum.apps) enso)
:- %wiki [%s wiki.apps]
:+ %static %o (malt (turn ~(tap by static.apps) enmso))
==
++ en-navbar |= a=navbar-app:sur
?@ a [%s a] [%s (cat 3 ['static/' +.a])]
++ enso |=(s=@t [%s s])
++ enmso |=([s=@tas t=@t] [s %s t])
:: ++ en-flag |= [name=@t =flag:tg :- %s %- crip "{(scow %p p.flag)}/{(trip q.flag)}"
--
++ de
=, dejs:format
|^
%- of
:~ dash+de-dash
site+de-site
==
++ de-dash
%- of
:~ set+de-set
del+pa
==
++ de-set
%- ot
:~ sitename+so
description+so
groupname+so
binding+pa
icon+so
home+so
css+so
apps+de-apps
app-order+(ar de-order)
hidden+bo
==
++ de-order |= j=json ^- navbar-app:sur
?> ?=(%s -.j)
=/ is-static .=('static/' (cut 3 [0 7] +.j))
?. is-static (app-type:sur +.j)
:- %static (cut 3 [7 (met 3 +.j)] +.j)
++ de-apps
%- ot
:~ blog+so
radio+bo
chat+(as so)
forum+(as so)
wiki+so
static+(om so)
==
++ de-site
%- ot
:~ name+so
:- %action de-site-action
==
++ de-site-action
%- of
:~
lol+so
search+de-search
==
++ de-search
=/ d di:dejs-soft:format
%- ot
:~ query+so
start+d
end+d
apps+(ar de-app)
by+soft-p
==
++ de-app |= j=json
?> ?=(%s -.j) (app-type:sur +.j)
++ soft-p |= j=json ^- (unit @p)
?. ?=(%s -.j) ~
(slaw %p +.j)
--
--

169
desk/lib/metamask.hoon Normal file
View File

@ -0,0 +1,169 @@
/+ naive, ethereum, server
=>
|%
+$ challenges (set secret)
+$ secret @uv
+$ authorization
$: who=@p
=secret
adr=tape
sig=tape
==
+$ user-sessions (map comet=@p id=@p)
+$ sessions
$: =challenges
users=user-sessions
==
--
|_ [=sessions =bowl:gall]
+$ sess ^sessions
:: state field to keep track of users logged with metamask
++ login ^- @p
=/ session (~(get by users.sessions) src.bowl)
?~ session src.bowl
u.session
:: this goes on the router
++ serve-metamask-challenge
|= eyre-id=@ta
:: special-case MetaMask auth handling
=/ new-challenge (sham [now eny]:bowl)
%+ weld (self-poke [%meta new-challenge])
%+ give-simple-payload:app:server
eyre-id
^- simple-payload:http
:- :- 200
~[['Content-Type' 'application/json']]
`(as-octs:mimes:html (en:json:html (enjs-challenge new-challenge)))
:: Modified from ~rabsef-bicrym's %mask by ~hanfel-dovned.
++ process-metamask-auth
|= [order-id=@t octs=(unit octs)]
^- (list card:agent:gall)
=/ challenges challenges.sessions
|^
?~ octs ~|(%empty-auth-request !!)
:: ?. =('auth' (cut 3 [0 4] q.u.octs))
:: *(list card:agent:gall)
=/ jon (de:json:html q.u.octs)
?~ jon ~|(%empty-auth-json !!)
=/ body=json u.jon
=/ axn (dejs-action body)
=/ is-valid (validate who.axn secret.axn adr.axn sig.axn)
~& >> signature-valid=[is-valid who.axn secret.axn adr.axn sig.axn]
?. is-valid ~|(%bad-metamask-signature !!)
%+ weld
(self-poke [%auth who.axn src.bowl secret.axn])
%+ give-simple-payload:app:server
order-id
^- simple-payload:http
:- :- 200
~[['Content-Type' 'application/json']]
=/ obj=json %- pairs:enjs:format :~([%login-ok [%b .y]])
`(as-octs:mimes:html (en:json:html obj))
++ validate
|= [who=@p challenge=secret address=tape hancock=tape]
^- ?
=/ addy (from-tape address)
=/ cock (from-tape hancock)
=/ owner (get-owner who) ?~ owner
~& "no owner"
%.n
?. =(addy u.owner)
~& "wrong owner"
%.n
?. (~(has in challenges) challenge)
~& "bad challenge"
%.n
=/ note=@uvI
=+ octs=(as-octs:mimes:html (scot %uv challenge))
%- keccak-256:keccak:crypto
%- as-octs:mimes:html
;: (cury cat 3)
'\19Ethereum Signed Message:\0a'
(crip (a-co:co p.octs))
q.octs
==
?. &(=(20 (met 3 addy)) =(65 (met 3 cock)))
~& "addy != cock"
%.n
=/ r (cut 3 [33 32] cock)
=/ s (cut 3 [1 32] cock)
=/ v=@
=+ v=(cut 3 [0 1] cock)
?+ v 99
%0 0
%1 1
%27 0
%28 1
==
?. |(=(0 v) =(1 v))
~& "wrong v"
%.n
=/ xy
(ecdsa-raw-recover:secp256k1:secp:crypto note v r s)
=/ pub :((cury cat 3) y.xy x.xy 0x4)
=/ add (address-from-pub:key:ethereum pub)
=(addy add)
::
++ from-tape
|=(h=tape ^-(@ux (scan h ;~(pfix (jest '0x') hex))))
::
++ get-owner
|= who=@p
^- (unit @ux)
=- ?~ pin=`(unit point:naive)`-
~
?. |(?=(%l1 dominion.u.pin) ?=(%l2 dominion.u.pin))
~
`address.owner.own.u.pin
.^ (unit point:naive)
%gx
%+ en-beam
[our.bowl %azimuth [%da now.bowl]]
/point/(scot %p who)/noun
==
++ dejs-action
|= jon=json
^- authorization
=, dejs:format
%. jon
%- ot
:~ [%who (se %p)]
[%secret (se %uv)]
[%address sa]
[%signature sa]
==
--
++ enjs-challenge
=, enjs:format
|= chal=@
^- json
%- pairs
:~ [%challenge [%s (scot %uv chal)]]
==
++ self-poke
|= noun=*
^- (list card:agent:gall)
:~ [%pass /gib %agent [our.bowl dap.bowl] %poke %noun !>(noun)]
==
:: these are the poke handlers
++ handle-meta
|= new-challenge=@ ^- ^sessions
=? users.sessions
!(~(has by users.sessions) src.bowl)
(~(put by users.sessions) [src.bowl src.bowl])
=? challenges.sessions
=(src.bowl (~(got by users.sessions) src.bowl))
(~(put in challenges.sessions) new-challenge)
sessions
++ handle-auth
|= [who=@p src=@p =secret] ^- ^sessions
~& > "%ustj: Successful authentication of {<src>} as {<who>}."
=. users.sessions (~(put by users.sessions) src who)
=. challenges.sessions (~(del in challenges.sessions) secret)
sessions
--

55
desk/lib/mip.hoon Normal file
View File

@ -0,0 +1,55 @@
|%
++ mip :: map of maps
|$ [kex key value]
(map kex (map key value))
::
++ bi :: mip engine
=| a=(map * (map))
|@
++ del
|* [b=* c=*]
=+ d=(~(gut by a) b ~)
=+ e=(~(del by d) c)
?~ e
(~(del by a) b)
(~(put by a) b e)
::
++ get
|* [b=* c=*]
=> .(b `_?>(?=(^ a) p.n.a)`b, c `_?>(?=(^ a) ?>(?=(^ q.n.a) p.n.q.n.a))`c)
^- (unit _?>(?=(^ a) ?>(?=(^ q.n.a) q.n.q.n.a)))
(~(get by (~(gut by a) b ~)) c)
::
++ got
|* [b=* c=*]
(need (get b c))
::
++ gut
|* [b=* c=* d=*]
(~(gut by (~(gut by a) b ~)) c d)
::
++ has
|* [b=* c=*]
!=(~ (get b c))
::
++ key
|* b=*
~(key by (~(gut by a) b ~))
::
++ put
|* [b=* c=* d=*]
%+ ~(put by a) b
%. [c d]
%~ put by
(~(gut by a) b ~)
::
++ tap
::NOTE naive turn-based implementation find-errors ):
=< $
=+ b=`_?>(?=(^ a) *(list [x=_p.n.a _?>(?=(^ q.n.a) [y=p v=q]:n.q.n.a)]))`~
|. ^+ b
?~ a
b
$(a r.a, b (welp (turn ~(tap by q.n.a) (lead p.n.a)) $(a l.a)))
--
--

926
desk/lib/naive.hoon Normal file
View File

@ -0,0 +1,926 @@
/+ tiny
!.
=> => tiny
:: Laconic bit
::
=| lac=?
:: Constants
::
|%
:: Transfers on L1 to this address count as depositing to L2
::
++ deposit-address 0x1111.1111.1111.1111.1111.1111.1111.1111.1111.1111
++ log-names
|%
:: Generated with (keccak-256:keccak:crypto (as-octs:mimes:html name))
::
:: OwnerChanged(uint32,address)
++ owner-changed
0x16d0.f539.d49c.6cad.822b.767a.9445.bfb1.
cf7e.a6f2.a6c2.b120.a7ea.4cc7.660d.8fda
::
:: Activated(uint32)
++ activated
0xe74c.0380.9d07.69e1.b1f7.06cc.8414.258c.
d1f3.b6fe.020c.d15d.0165.c210.ba50.3a0f
::
:: Spawned(uint32,uint32)
++ spawned
0xb2d3.a6e7.a339.f5c8.ff96.265e.2f03.a010.
a854.1070.f374.4a24.7090.9644.1508.1546
::
:: OwnershipTransferred(address,address)
++ ownership-transferred
0x8be0.079c.5316.5914.1344.cd1f.d0a4.f284.
1949.7f97.22a3.daaf.e3b4.186f.6b64.57e0
::
:: EscapeRequested(uint32,uint32)
++ escape-requested
0xb4d4.850b.8f21.8218.141c.5665.cba3.79e5.
3e9b.b015.b51e.8d93.4be7.0210.aead.874a
::
:: EscapeCanceled(uint32,uint32)
++ escape-canceled
0xd653.bb0e.0bb7.ce83.93e6.24d9.8fbf.17cd.
a590.2c83.28ed.0cd0.9988.f368.90d9.932a
::
:: EscapeAccepted(uint32,uint32)
++ escape-accepted
0x7e44.7c9b.1bda.4b17.4b07.96e1.00bf.7f34.
ebf3.6dbb.7fe6.6549.0b1b.fce6.246a.9da5
::
:: LostSponsor(uint32,uint32)
++ lost-sponsor
0xd770.4f9a.2519.3dbd.0b0c.b4a8.09fe.ffff.
a7f1.9d1a.ae88.17a7.1346.c194.4482.10d5
::
:: ChangedKeys(uint32,bytes32,bytes32,uint32,uint32)
++ changed-keys
0xaa10.e7a0.117d.4323.f1d9.9d63.0ec1.69be.
bb3a.988e.8957.70e3.5198.7e01.ff54.23d5
::
:: BrokeContinuity(uint32,uint32)
++ broke-continuity
0x2929.4799.f1c2.1a37.ef83.8e15.f79d.d91b.
cee2.df99.d63c.d1c1.8ac9.68b1.2951.4e6e
::
:: ChangedSpawnProxy(uint32,address)
++ changed-spawn-proxy
0x9027.36af.7b3c.efe1.0d9e.840a.ed0d.687e.
35c8.4095.122b.2505.1a20.ead8.866f.006d
::
:: ChangedTransferProxy(uint32,address)
++ changed-transfer-proxy
0xcfe3.69b7.197e.7f0c.f067.93ae.2472.a9b1.
3583.fecb.ed2f.78df.a14d.1f10.796b.847c
::
:: ChangedManagementProxy(uint32,address)
++ changed-management-proxy
0xab9c.9327.cffd.2acc.168f.afed.be06.139f.
5f55.cb84.c761.df05.e051.1c25.1e2e.e9bf
::
:: ChangedVotingProxy(uint32,address)
++ changed-voting-proxy
0xcbd6.269e.c714.57f2.c7b1.a227.74f2.46f6.
c5a2.eae3.795e.d730.0db5.1768.0c61.c805
::
:: ChangedDns(string,string,string)
++ changed-dns
0xfafd.04ad.e1da.ae2e.1fdb.0fc1.cc6a.899f.
d424.063e.d5c9.2120.e67e.0730.53b9.4898
::
:: ApprovalForAll(address,address,bool)
++ approval-for-all
0x1730.7eab.39ab.6107.e889.9845.ad3d.59bd.
9653.f200.f220.9204.89ca.2b59.3769.6c31
--
-- =>
:: Types
|%
:: ethereum address, 20 bytes.
::
+$ address @ux
+$ nonce @ud
+$ dominion ?(%l1 %l2 %spawn)
+$ keys [=life suite=@ud auth=@ crypt=@]
++ orm ((on ship point) por)
++ point
$: :: domain
::
=dominion
::
:: ownership
::
$= own
$: owner=[=address =nonce]
spawn-proxy=[=address =nonce]
management-proxy=[=address =nonce]
voting-proxy=[=address =nonce]
transfer-proxy=[=address =nonce]
==
::
:: networking
::
$= net
$: rift=@ud
=keys
sponsor=[has=? who=@p]
escape=(unit @p)
==
==
::
++ diff
$% [%nonce =ship =proxy =nonce]
[%tx =raw-tx err=(unit @tas)]
[%operator owner=address operator=address approved=?]
[%dns domains=(list @t)]
$: %point =ship
$% [%rift =rift]
[%keys =keys]
[%sponsor sponsor=(unit @p)]
[%escape to=(unit @p)]
[%owner =address]
[%spawn-proxy =address]
[%management-proxy =address]
[%voting-proxy =address]
[%transfer-proxy =address]
[%dominion =dominion]
== == ==
::
+$ state
$: %0
=points
=operators
dns=(list @t)
==
+$ points (tree [ship point])
+$ operators (jug address address)
+$ effects (list diff)
+$ proxy ?(%own %spawn %manage %vote %transfer)
+$ roll (list raw-tx)
+$ raw-tx [sig=@ raw=octs =tx]
+$ tx [from=[=ship =proxy] skim-tx]
+$ skim-tx
$% [%transfer-point =address reset=?]
[%spawn =ship =address]
[%configure-keys encrypt=@ auth=@ crypto-suite=@ breach=?]
[%escape parent=ship]
[%cancel-escape parent=ship]
[%adopt =ship]
[%reject =ship]
[%detach =ship]
[%set-management-proxy =address]
[%set-spawn-proxy =address]
[%set-transfer-proxy =address]
==
::
+$ event-log
$: address=@ux
data=@ux
topics=(lest @ux)
==
+$ input
$: block=@ud
$% [%bat batch=@]
[%log =event-log]
== ==
:: ECDSA verifier.
::
:: Must keccak `dat` and recover the ethereum address which signed.
:: Must not crash. `v` will normally be between 0 and 3; if it is not,
:: should produce null.
::
+$ verifier $-([dat=octs v=@ r=@ s=@] (unit address))
-- =>
::
|%
++ debug
|* [meg=@t *]
?: lac
+<+
~> %slog.[0 meg]
+<+
::
++ parse-roll
|= batch=@
=| =roll
=| pos=@ud
=/ las (met 0 batch)
|- ^+ roll
?: (gte pos las)
(flop roll)
=/ parse-result (parse-raw-tx pos batch)
:: Parsing failed, abort batch
::
?~ parse-result
(debug %parse-failed ~)
=^ =raw-tx pos u.parse-result
$(roll [raw-tx roll])
::
++ parse-raw-tx
|= [pos=@ud batch=@]
^- (unit [raw-tx pos=@ud])
|^
=^ sig pos (take 3 65)
=/ res=(unit [=tx pos=@ud]) parse-tx
?~ res ~
=/ dif (sub pos.u.res pos)
=/ len =>((dvr dif 8) ?>(=(0 q) p))
:- ~ :_ pos.u.res
[sig [len (cut 0 [pos dif] batch)] tx.u.res]
::
++ parse-tx
^- (unit [tx pos=@ud])
=^ from-proxy=@ pos (take 0 3)
?. ?=(?(%0 %1 %2 %3 %4) from-proxy) (debug %bad-proxy ~)
=/ =proxy
?- from-proxy
%0 %own
%1 %spawn
%2 %manage
%3 %vote
%4 %transfer
==
=^ pad pos (take 0 5)
=^ from-ship=ship pos (take 3 4)
=- ?~ res
~
`[[[from-ship proxy] skim-tx.u.res] pos.u.res]
^- res=(unit [=skim-tx pos=@ud])
=^ op pos (take 0 7)
?+ op (debug %strange-opcode ~)
%0
=^ reset=@ pos (take 0)
=^ =address pos (take 3 20)
`[[%transfer-point address =(0 reset)] pos]
::
%1
=^ pad=@ pos (take 0)
=^ =ship pos (take 3 4)
=^ =address pos (take 3 20)
`[[%spawn ship address] pos]
::
%2
=^ breach=@ pos (take 0)
=^ encrypt=@ pos (take 3 32)
=^ auth=@ pos (take 3 32)
=^ crypto-suite=@ pos (take 3 4)
`[[%configure-keys encrypt auth crypto-suite =(0 breach)] pos]
::
%3 =^(res pos take-ship `[[%escape res] pos])
%4 =^(res pos take-ship `[[%cancel-escape res] pos])
%5 =^(res pos take-ship `[[%adopt res] pos])
%6 =^(res pos take-ship `[[%reject res] pos])
%7 =^(res pos take-ship `[[%detach res] pos])
%8 =^(res pos take-address `[[%set-management-proxy res] pos])
%9 =^(res pos take-address `[[%set-spawn-proxy res] pos])
%10 =^(res pos take-address `[[%set-transfer-proxy res] pos])
==
::
:: Take a bite
::
++ take
|= =bite
^- [@ @ud]
=/ =step
?@ bite (bex bite)
(mul step.bite (bex bloq.bite))
[(cut 0 [pos step] batch) (add pos step)]
:: Encode ship and address
::
++ take-address
^- [address @ud]
=^ pad=@ pos (take 0)
=^ =address pos (take 3 20)
[address pos]
:: Encode escape-related txs
::
++ take-ship
^- [ship @ud]
=^ pad=@ pos (take 0)
=^ other=ship pos (take 3 4)
[other pos]
--
::
++ proxy-from-point
|= [=proxy point]
^- [=address =nonce]
?- proxy
%own owner.own
%spawn spawn-proxy.own
%manage management-proxy.own
%vote voting-proxy.own
%transfer transfer-proxy.own
==
::
++ verify-sig-and-nonce
|= [=verifier chain-t=@t =state =raw-tx]
^- ?
|^
=/ point (get-point state ship.from.tx.raw-tx)
?> ?=(^ point) :: we never parse more than four bytes for a ship
=/ need=[=address =nonce]
(proxy-from-point proxy.from.tx.raw-tx u.point)
:: We include a domain separator to avoid letting signatures be
:: accidentally reused with other applications. We include the name
:: UrbitID, a signature format version number, and the EIP-155 chain
:: ID.
::
:: We also include a nonce so that a transaction cannot be
:: rebroadcast.
::
=/ prepared-data=octs
%: cad 3
14^'UrbitIDV1Chain'
(met 3 chain-t)^chain-t
1^':'
4^nonce.need
raw.raw-tx
~
==
:: Wallets which support personal_sign include this preamble to avoid
:: letting personal_sign be used to sign ethereum transactions
::
=/ signed-data=octs
=/ len (ud-to-ascii p.prepared-data)
%: cad 3
26^'\19Ethereum Signed Message:\0a'
(met 3 len)^len
prepared-data
~
==
=/ dress (verify-sig sig.raw-tx signed-data)
?~ dress
|
=(address.need u.dress)
:: Verify signature and produce signer address
::
++ verify-sig
|= [sig=@ txdata=octs]
^- (unit address)
|^
:: Reversed of the usual r-s-v order because Ethereum integers are
:: big-endian
::
=^ v sig (take 3)
=^ s sig (take 3 32)
=^ r sig (take 3 32)
:: In Ethereum, v is generally 27 + recid, and verifier expects a
:: recid. Old versions of geth used 0 + recid, so most software
:: now supports either format. See:
::
:: https://github.com/ethereum/go-ethereum/issues/2053
::
=? v (gte v 27) (sub v 27)
(verifier txdata v r s)
::
++ take
|= =bite
[(end bite sig) (rsh bite sig)]
--
--
:: ASCII-decimal encode
::
++ ud-to-ascii
|= n=@ud
?~ n '0'
=| l=(list @)
|- ^- @t
?~ n (rep 3 l)
=+ (dvr n 10)
$(n p, l [(add '0' q) l])
::
++ ship-rank
|= =ship
^- ?(%0 %1 %2 %3 %4)
?: (lth ship 0x100) %0
?: (lth ship 0x1.0000) %1
?: (lth ship 0x1.0000.0000) %2
?: (lth ship 0x1.0000.0000.0000.0000) %3
%4
::
++ sein :: autoboss
|= who=ship
^- ship
=/ mir (ship-rank who)
?- mir
%0 who
%1 (end 3 who)
%2 (end 4 who)
%3 (end 5 who)
%4 (end 4 who)
==
::
:: Produces null only if ship is not a galaxy, star, or planet
::
++ get-point
|= [=state =ship]
^- (unit point)
=/ existing (get:orm points.state ship)
?^ existing
`u.existing
=| =point
=. who.sponsor.net.point (sein ship)
?+ (ship-rank ship) (debug %strange-point ~)
%0 `point(dominion %l1)
?(%1 %2)
=/ existing-parent $(ship (sein ship))
?~ existing-parent ~
:- ~
%= point
dominion
?- dominion.u.existing-parent
%l1 %l1
%l2 %l2
%spawn %l2
==
==
==
-- =>
|%
:: Receive log from L1 transaction
::
++ receive-log
|= [=state log=event-log]
^- [effects ^state]
=* log-name i.topics.log
?: =(log-name activated:log-names) `state
?: =(log-name spawned:log-names) `state
?: =(log-name ownership-transferred:log-names) `state
?: =(log-name changed-dns:log-names)
?> ?=(~ t.topics.log)
=/ words (rip 8 data.log)
:: This is only true if each domain is <= 32 bytes
::
?. ?=([c=@ @ b=@ @ a=@ @ @ @ @ ~] words) `state
=* one &5.words
=* two &3.words
=* tri &1.words
=/ domains ~[(swp 3 one) (swp 3 two) (swp 3 tri)]
:- [%dns domains]~
state(dns domains)
::
?: =(log-name approval-for-all:log-names)
?> ?=([@ @ ~] t.topics.log)
=* owner i.t.topics.log
=* operator i.t.t.topics.log
=/ approved !=(0 data.log)
:- [%operator owner operator approved]~
=- state(operators -)
?: approved
(~(put ju operators.state) owner operator)
(~(del ju operators.state) owner operator)
::
:: The rest of the logs modify a particular ship, specified in the
:: second topic. We fetch it, and insert the modification back into
:: our state.
::
?> ?=([@ *] t.topics.log)
=* ship=@ i.t.topics.log
=/ the-point (get-point state ship)
?> ?=(^ the-point)
=* point u.the-point
::
:: Important to fully no-op on failure so we don't insert an entry
:: into points.state
::
=- ?~ res
`state
[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
^- res=(unit [=effects new-point=^point])
::
?: =(log-name changed-spawn-proxy:log-names)
?. ?=(%l1 -.point) ~
?> ?=([@ ~] t.t.topics.log)
=* to i.t.t.topics.log
:: Depositing to L2 is represented by a spawn proxy change on L1,
:: but it doesn't change the actual spawn proxy.
::
?: =(deposit-address to)
:+ ~ [%point ship %dominion %spawn]~
point(dominion %spawn)
:+ ~ [%point ship %spawn-proxy to]~
point(address.spawn-proxy.own to)
::
?: =(log-name escape-accepted:log-names)
?> ?=([@ ~] t.t.topics.log)
=* parent=@ i.t.t.topics.log
=/ parent-point (get-point state parent)
?> ?=(^ parent-point)
?: ?=(%l2 -.u.parent-point) ~
:+ ~ [%point ship %sponsor `parent]~
point(escape.net ~, sponsor.net [%& parent])
::
?: =(log-name lost-sponsor:log-names)
?> ?=([@ ~] t.t.topics.log)
=* parent=@ i.t.t.topics.log
:: If the sponsor we lost was not our actual sponsor, we didn't
:: actually lose anything.
::
?. =(parent who.sponsor.net.point) ~
::
=/ parent-point (get-point state parent)
?> ?=(^ parent-point)
::
:: We can detach even if the child is on L2, as long as the parent
:: is on L1.
::
?: ?=(%l2 -.u.parent-point) ~
:+ ~ [%point ship %sponsor ~]~
point(has.sponsor.net %|)
::
:: The rest can be done by any ship on L1, even if their spawn proxy
:: is set to L2
::
?: ?=(%l2 -.point) ~
::
?: =(log-name escape-requested:log-names)
?> ?=([@ ~] t.t.topics.log)
=* parent=@ i.t.t.topics.log
=/ parent-point (get-point state parent)
?> ?=(^ parent-point)
:+ ~ [%point ship %escape `parent]~
point(escape.net `parent)
::
?: =(log-name escape-canceled:log-names)
?> ?=([@ ~] t.t.topics.log)
=* parent=@ i.t.t.topics.log
=/ parent-point (get-point state parent)
?> ?=(^ parent-point)
:+ ~ [%point ship %escape ~]~
point(escape.net ~)
::
?: =(log-name broke-continuity:log-names)
?> ?=(~ t.t.topics.log)
=* rift=@ data.log
:+ ~ [%point ship %rift rift]~
point(rift.net rift)
::
?: =(log-name changed-keys:log-names)
?> ?=(~ t.t.topics.log)
=/ =keys
:* life=(cut 8 [0 1] data.log)
suite=(cut 8 [1 1] data.log)
auth=(cut 8 [2 1] data.log)
crypt=(cut 8 [3 1] data.log)
==
:+ ~ [%point ship %keys keys]~
point(keys.net keys)
::
?: =(log-name owner-changed:log-names)
?> ?=([@ ~] t.t.topics.log)
=* to i.t.t.topics.log
:: Depositing to L2 is represented by an ownership change on L1,
:: but it doesn't change who actually owns the ship.
::
?: =(deposit-address to)
:+ ~ [%point ship %dominion %l2]~
point(dominion %l2)
:+ ~ [%point ship %owner to]~
point(address.owner.own to)
::
?: =(log-name changed-transfer-proxy:log-names)
?> ?=([@ ~] t.t.topics.log)
=* to i.t.t.topics.log
:+ ~ [%point ship %transfer-proxy to]~
point(address.transfer-proxy.own to)
::
?: =(log-name changed-management-proxy:log-names)
?> ?=([@ ~] t.t.topics.log)
=* to i.t.t.topics.log
:+ ~ [%point ship %management-proxy to]~
point(address.management-proxy.own to)
::
?: =(log-name changed-voting-proxy:log-names)
?> ?=([@ ~] t.t.topics.log)
=* to i.t.t.topics.log
:+ ~ [%point ship %voting-proxy to]~
point(address.voting-proxy.own to)
::
(debug %unknown-log ~)
::
:: Receive batch of L2 transactions
::
++ receive-batch
|= [=verifier chain-id=@ud =state batch=@]
=/ chain-t (ud-to-ascii chain-id)
=/ =roll (parse-roll batch)
|- ^- [effects ^state]
?~ roll
[~ state]
:: Verify signature, else skip tx
::
?. (verify-sig-and-nonce verifier chain-t state i.roll)
%+ debug %l2-sig-failed
=^ effects state $(roll t.roll)
:_ state
[[%tx i.roll `%sig-or-nonce-failed] effects]
:: Increment nonce, even if it later fails
::
=^ effects-1 points.state (increment-nonce state from.tx.i.roll)
:: Process tx
::
=^ effects-2 state
=/ tx-result=(unit [=effects =^state]) (receive-tx state tx.i.roll)
?~ tx-result
%+ debug %l2-tx-failed
[[%tx i.roll `%tx-failed]~ state]
[[[%tx i.roll ~] effects.u.tx-result] state.u.tx-result]
=^ effects-3 state $(roll t.roll)
[:(welp effects-1 effects-2 effects-3) state]
::
++ increment-nonce
|= [=state =ship =proxy]
=/ point (get-point state ship)
?> ?=(^ point) :: we only parsed 4 bytes
=* own own.u.point
=^ nonce u.point
?- proxy
%own
:- nonce.owner.own
u.point(nonce.owner.own +(nonce.owner.own))
::
%spawn
:- nonce.spawn-proxy.own
u.point(nonce.spawn-proxy.own +(nonce.spawn-proxy.own))
::
%manage
:- nonce.management-proxy.own
u.point(nonce.management-proxy.own +(nonce.management-proxy.own))
::
%vote
:- nonce.voting-proxy.own
u.point(nonce.voting-proxy.own +(nonce.voting-proxy.own))
::
%transfer
:- nonce.transfer-proxy.own
u.point(nonce.transfer-proxy.own +(nonce.transfer-proxy.own))
==
::
:- [%nonce ship proxy nonce]~
(put:orm points.state ship u.point)
::
:: Receive an individual L2 transaction
::
++ receive-tx
|= [=state =tx]
|^
^- (unit [effects ^state])
?- +<.tx
%spawn (process-spawn +>.tx)
%transfer-point (w-point process-transfer-point ship.from.tx +>.tx)
%configure-keys (w-point process-configure-keys ship.from.tx +>.tx)
%escape (w-point-esc process-escape ship.from.tx +>.tx)
%cancel-escape (w-point-esc process-cancel-escape ship.from.tx +>.tx)
%adopt (w-point-esc process-adopt ship.tx +>.tx)
%reject (w-point-esc process-reject ship.tx +>.tx)
%detach (w-point-esc process-detach ship.tx +>.tx)
%set-spawn-proxy
(w-point-spawn process-set-spawn-proxy ship.from.tx +>.tx)
::
%set-transfer-proxy
(w-point process-set-transfer-proxy ship.from.tx +>.tx)
::
%set-management-proxy
(w-point process-set-management-proxy ship.from.tx +>.tx)
==
::
++ w-point
|* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
^- (unit [effects ^state])
=/ point (get-point state ship)
?~ point (debug %strange-ship ~)
?. ?=(%l2 -.u.point) (debug %ship-not-on-l2 ~)
:: Important to fully no-op on failure so we don't insert an entry
:: into points.state
::
=/ res=(unit [=effects new-point=^point]) (fun u.point rest)
?~ res
~
`[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
::
++ w-point-esc
|* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
^- (unit [effects ^state])
=/ point (get-point state ship)
?~ point (debug %strange-ship ~)
=/ res=(unit [=effects new-point=^point]) (fun u.point rest)
?~ res
~
`[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
::
++ w-point-spawn
|* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
^- (unit [effects ^state])
=/ point (get-point state ship)
?~ point (debug %strange-ship ~)
?: ?=(%l1 -.u.point) (debug %ship-on-l2 ~)
=/ res=(unit [=effects new-point=^point]) (fun u.point rest)
?~ res
~
`[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
::
++ process-transfer-point
|= [=point to=address reset=?]
=* ship ship.from.tx
:: Assert from owner or transfer prxoy
::
?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
(debug %bad-permission ~)
:: Execute transfer
::
=/ effects-1
~[[%point ship %owner to] [%point ship %transfer-proxy *address]]
=: address.owner.own.point to
address.transfer-proxy.own.point *address
==
:: Execute reset if requested
::
?. reset
`[effects-1 point]
::
=^ effects-2 net.point
?: =([0 0 0] +.keys.net.point)
`net.point
=/ =keys [+(life.keys.net.point) 0 0 0]
:- [%point ship %keys keys]~
[rift.net.point keys sponsor.net.point escape.net.point]
=^ effects-3 rift.net.point
?: =(0 life.keys.net.point)
`rift.net.point
:- [%point ship %rift +(rift.net.point)]~
+(rift.net.point)
=/ effects-4
:~ [%point ship %spawn-proxy *address]
[%point ship %management-proxy *address]
[%point ship %voting-proxy *address]
[%point ship %transfer-proxy *address]
==
=: address.spawn-proxy.own.point *address
address.management-proxy.own.point *address
address.voting-proxy.own.point *address
address.transfer-proxy.own.point *address
==
`[:(welp effects-1 effects-2 effects-3 effects-4) point]
::
++ process-spawn
|= [=ship to=address]
^- (unit [effects ^state])
=/ parent=^ship (sein ship)
:: Assert parent is on L2
::
=/ parent-point (get-point state parent)
?~ parent-point ~
?. ?=(?(%l2 %spawn) -.u.parent-point) ~
:: Assert from owner or spawn proxy
::
?. ?& =(parent ship.from.tx)
|(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
==
(debug %bad-permission ~)
:: Assert child not already spawned
::
?^ (get:orm points.state ship) (debug %spawn-exists ~)
:: Assert one-level-down
::
?. =(+((ship-rank parent)) (ship-rank ship)) (debug %bad-rank ~)
::
=/ [=effects new-point=point]
=/ point=(unit point) (get-point state ship)
?> ?=(^ point) :: only parsed 4 bytes
:: If spawning to self, just do it
::
?: ?| ?& =(%own proxy.from.tx)
=(to address.owner.own.u.parent-point)
==
?& =(%spawn proxy.from.tx)
=(to address.spawn-proxy.own.u.parent-point)
==
==
:- ~[[%point ship %dominion %l2] [%point ship %owner to]]
u.point(address.owner.own to)
:: Else spawn to parent and set transfer proxy
::
:- :~ [%point ship %dominion %l2]
[%point ship %owner address.owner.own.u.parent-point]
[%point ship %transfer-proxy to]
==
%= u.point
address.owner.own address.owner.own.u.parent-point
address.transfer-proxy.own to
==
`[effects state(points (put:orm points.state ship new-point))]
::
++ process-configure-keys
|= [=point crypt=@ auth=@ suite=@ breach=?]
=* ship ship.from.tx
::
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
=^ rift-effects rift.net.point
?. breach
`rift.net.point
[[%point ship %rift +(rift.net.point)]~ +(rift.net.point)]
::
=^ keys-effects keys.net.point
?: =(+.keys.net.point [suite auth crypt])
`keys.net.point
=/ =keys
[+(life.keys.net.point) suite auth crypt]
[[%point ship %keys keys]~ keys]
::
`[(welp rift-effects keys-effects) point]
::
++ process-escape
|= [=point parent=ship]
=* ship ship.from.tx
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
?. =(+((ship-rank parent)) (ship-rank ship))
(debug %bad-rank ~)
::
:+ ~ [%point ship %escape `parent]~
point(escape.net `parent)
::
++ process-cancel-escape
|= [=point parent=ship]
=* ship ship.from.tx
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
:+ ~ [%point ship %escape ~]~
point(escape.net ~)
::
++ process-adopt
|= [=point =ship]
=* parent ship.from.tx
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
?. =(escape.net.point `parent) (debug %no-adopt ~)
:+ ~ [%point ship %sponsor `parent]~
point(escape.net ~, sponsor.net [%& parent])
::
++ process-reject
|= [=point =ship]
=* parent ship.from.tx
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
?. =(escape.net.point `parent) (debug %no-reject ~)
:+ ~ [%point ship %escape ~]~
point(escape.net ~)
::
++ process-detach
|= [=point =ship]
=* parent ship.from.tx
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
?. =(who.sponsor.net.point parent) (debug %no-detach ~)
:+ ~ [%point ship %sponsor ~]~
point(has.sponsor.net %|)
::
++ process-set-management-proxy
|= [=point =address]
?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
(debug %bad-permission ~)
::
:+ ~ [%point ship.from.tx %management-proxy address]~
point(address.management-proxy.own address)
::
++ process-set-spawn-proxy
|= [=point =address]
?. |(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
(debug %bad-permission ~)
::
?: (gte (ship-rank ship.from.tx) 2)
(debug %spawn-proxy-planet ~)
::
:+ ~ [%point ship.from.tx %spawn-proxy address]~
point(address.spawn-proxy.own address)
::
++ process-set-transfer-proxy
|= [=point =address]
?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
(debug %bad-permission ~)
::
:+ ~ [%point ship.from.tx %transfer-proxy address]~
point(address.transfer-proxy.own address)
--
--
::
:: State transition function
::
|= [=verifier chain-id=@ud =state =input]
^- [effects ^state]
?: ?=(%log +<.input)
:: Received log from L1 transaction
::
(receive-log state event-log.input)
:: Received L2 batch
::
:: %+ debug %batch
(receive-batch verifier chain-id state batch.input)

8
desk/lib/seed.hoon Normal file
View File

@ -0,0 +1,8 @@
|%
++ text ^- (list @t)
:~
'''
You
'''
==
--

174
desk/lib/server.hoon Normal file
View File

@ -0,0 +1,174 @@
=, eyre
|%
+$ request-line
$: [ext=(unit @ta) site=(list @t)]
args=(list [key=@t value=@t])
==
:: +parse-request-line: take a cord and parse out a url
::
++ parse-request-line
|= url=@t
^- request-line
(fall (rush url ;~(plug apat:de-purl:html yque:de-purl:html)) [[~ ~] ~])
::
++ manx-to-octs
|= man=manx
^- octs
(as-octt:mimes:html (en-xml:html man))
::
++ json-to-octs
|= jon=json
^- octs
(as-octs:mimes:html (en:json:html jon))
::
++ app
|%
::
:: +require-authorization:
:: redirect to the login page when unauthenticated
:: otherwise call handler on inbound request
::
++ require-authorization
|= $: =inbound-request:eyre
handler=$-(inbound-request:eyre simple-payload:http)
==
^- simple-payload:http
::
?: authenticated.inbound-request
~! this
~! +:*handler
(handler inbound-request)
::
=- [[307 ['location' -]~] ~]
%^ cat 3
'/~/login?redirect='
url.request.inbound-request
::
:: +require-authorization-simple:
:: redirect to the login page when unauthenticated
:: otherwise pass through simple-paylod
::
++ require-authorization-simple
|= [=inbound-request:eyre =simple-payload:http]
^- simple-payload:http
::
?: authenticated.inbound-request
~! this
simple-payload
::
=- [[307 ['location' -]~] ~]
%^ cat 3
'/~/login?redirect='
url.request.inbound-request
::
++ give-simple-payload
|= [eyre-id=@ta =simple-payload:http]
^- (list card:agent:gall)
=/ header-cage
[%http-response-header !>(response-header.simple-payload)]
=/ data-cage
[%http-response-data !>(data.simple-payload)]
:~ [%give %fact ~[/http-response/[eyre-id]] header-cage]
[%give %fact ~[/http-response/[eyre-id]] data-cage]
[%give %kick ~[/http-response/[eyre-id]] ~]
==
--
++ core
|%
++ gate
=| opt=@
|= a=@ opt
:: ++ try gate(opt 5)
++ try2
=/ g gate
g(opt 5)
--
++ gen
|%
::
++ max-1-da ['cache-control' 'max-age=86400']
:: ++ max-1-wk ['cache-control' 'max-age=604800']
++ max-1-wk ['cache-control' 'no-cache, no-store, must-revalidate']
++ no-cache ['cache-control' 'no-cache, no-store, must-revalidate']
::
++ lol
=| lmao=?
|= a=* %lol
++ html-response
=| cache=?
|= =octs
^- simple-payload:http
:_ `octs
[200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]]
::
++ css-response
=| cache=?
|= =octs
^- simple-payload:http
:_ `octs
[200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]]
::
++ js-response
=| cache=?
|= =octs
^- simple-payload:http
:_ `octs
[200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]]
::
++ png-response
=| cache=?
|= =octs
^- simple-payload:http
:_ `octs
[200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]]
::
++ svg-response
=| cache=?
|= =octs
^- simple-payload:http
:_ `octs
[200 [['content-type' 'image/svg+xml'] ?:(cache [max-1-wk ~] ~)]]
::
++ ico-response
|= =octs
^- simple-payload:http
[[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs]
::
++ woff2-response
=| cache=?
|= =octs
^- simple-payload:http
[[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs]
::
++ json-response
=| cache=_|
|= =json
^- simple-payload:http
:_ `(json-to-octs json)
[200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]]
::
++ manx-response
=| cache=_|
|= man=manx
^- simple-payload:http
:_ `(manx-to-octs man)
[200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]]
::
++ not-found
^- simple-payload:http
[[404 ~] ~]
::
++ login-redirect
|= =request:http
^- simple-payload:http
=- [[307 ['location' -]~] ~]
%^ cat 3
'/~/login?redirect='
url.request
::
++ redirect
|= redirect=cord
^- simple-payload:http
[[303 ['location' redirect]~] ~]
--
--

227
desk/lib/sigil/sigil.hoon Normal file
View File

@ -0,0 +1,227 @@
:: sigil: @p svg generation
::
:: usage: do a named import, then invoke as a function:
:: (sigil ~zod)
::
:: optionally modify configuration parameters:
:: %. ~paldev
:: %_ sigil
:: size 25
:: fg "black"
:: bg "#2aa779"
:: margin |
:: icon &
:: ==
::
::NOTE svg construction logic is coupled to the symbols definitions.
:: the symbols' elements assume they live in a space of 128x128.
:: what we do here is assume an svg _canvas_ of 128x128, draw the
:: symbols at their original sizes, and then scale them down to fit.
::
/+ sigil-symbols
::
:: config
::
=/ fg=tape "white"
=/ bg=tape "black"
=/ size=@ud 128
=/ margin=? &
=/ icon=? |
::
::
~% %sigil ..part ~
=/ who=ship ~zod
=/ syc=@ud 1
|^ |= =ship
^- manx
::
=. who ship
=/ syz (simp who)
=. syc (lent syz)
:: shift the sigil to account for the margin
:: scale the sigil to account for the amount of symbols
::
=/ sire=@rd (sun:rd size)
=/ tr=tape
::TODO render transform inside +sigil:svg?
%+ transform:svg
?. margin ~
=+ grid:pos
`[(gird:pos x) (gird:pos y)]
`span:pos
::
=/ sw=@rd ::TODO
?: icon .~0.8 ::TODO scale with size?
(add:rd .~0.33 (div:rd sire .~128))
::
%- outer:svg
%+ sigil:svg
[tr sw]
(symbols:svg syz)
::
++ pos
|%
++ span :: symbol scale (relative to full canvas)
^- @rd
::TODO accounting for margin here feels a bit ugly?
?+ (max grid) !!
%1 ?:(margin .~0.4 .~1)
%2 ?:(margin .~0.4 .~0.5)
%4 ?:(margin .~0.2 .~0.25)
==
::
++ grid :: size in symbols
^- [x=@ud y=@ud]
?+ syc !!
%16 [4 4]
%8 [4 4]
%4 [2 2]
%2 [2 1]
%1 [1 1]
==
::
++ gird :: calculate margin
|= n=@ud
^- @rd
=- (div:rd - .~2) :: / both sides
%+ sub:rd .~128 :: canvas size -
%+ mul:rd (sun:rd n) :: symbols *
%+ mul:rd span:pos :: symbol scale *
.~128 :: symbol size
::
++ plan :: as position on symbol grid
|= i=@ud
^- [x=@ud y=@ud]
?+ [syc i] !!
[%16 *] [(mod i 4) (div i 4)]
::
[%8 %0] [0 0]
[%8 %1] [3 0]
[%8 %2] [0 3]
[%8 %3] [3 3]
[%8 %4] [1 1]
[%8 %5] [2 1]
[%8 %6] [1 2]
[%8 %7] [2 2]
::
[%4 *] [(mod i 2) (div i 2)]
[%2 *] [i 0]
[%1 *] [0 0]
==
--
::
++ svg
|%
++ outer
|= inner=manx
^- manx
=/ s=tape ((d-co:co 1) size)
;svg
=style "display: block; width: {s}px; height: {s}px;" :: prevent bottom margin on svg tag
=width s
=height s
=viewBox "0 0 128 128"
=version "1.1"
=xmlns "http://www.w3.org/2000/svg"
::TODO additional attributes from config arg?
;rect
=fill bg
=width "128"
=height "128";
;+ inner
==
::
::TODO should it be possible to get these svg elements out of this lib?
++ sigil
|= [[tr=tape sw=@rd] symbols=(list manx)]
^- manx
;g
=transform tr
=stroke-width (say-rd sw)
=stroke-linecap "square"
=fill fg
=stroke bg
::NOTE unfortunately, vector-effect cannot be inherited,
:: so it gets inlined in the symbol elements instead
:: =vector-effect "non-scaling-stroke"
;* symbols
==
::
++ symbols
|= noms=(list @t)
^- (list manx)
=| i=@ud
=/ l=@ud (lent noms)
|-
?~ noms ~
:_ $(noms t.noms, i +(i))
::TODO exclude if both 0
=+ (plan:pos i)
;g(transform (transform `[(sun:rd (mul x 128)) (sun:rd (mul y 128))] ~))
;* =+ ((symbol i.noms) fg bg)
?.(icon - (scag 1 -))
==
::
++ symbol ~(got by sigil-symbols)
::
++ transform ::TODO take manx instead so we can omit attr entirely?
|= [translate=(unit [x=@rd y=@rd]) scale=(unit @rd)]
^- tape
%- zing
^- (list tape)
::TODO make cleaner
=- ?: ?=(?(~ [* ~]) -) -
(join " " `(list tape)`-)
^- (list tape)
:~ ?~ translate ~
?: =([0 0] u.translate) ~
"translate({(say-rd x.u.translate)} {(say-rd y.u.translate)})"
::
?~ scale ~
"scale({(say-rd u.scale)})"
==
--
::
++ simp
|= =ship
^- (list @t)
:: split into phonemes
::
=/ noms=(list @t)
=/ nom=@t
(rsh 3 (scot %p ship))
|- ?~ nom ~
|- ?: =('-' (end 3 nom))
$(nom (rsh 3 nom))
:- (end 3^3 nom)
^$(nom (rsh 3^3 nom))
:: fill leading empties with 'zod'
::
=/ left=@ud
=- (sub - (lent noms))
%- bex
?- (clan:title ship)
%czar 0
%king 1
%duke 2
%earl 3
%pawn 4
==
|-
?: =(0 left) noms
$(noms ['zod' noms], left (dec left))
::
++ rd ~(. ^rd %n)
++ say-rd
|= n=@rd
^- tape
=/ =dn (drg:rd n)
?> ?=(%d -.dn)
=/ [s=? m=@ud] (old:si e.dn)
=/ x=@ud (pow 10 m)
%+ weld
%- (d-co:co 1)
?:(s (mul a.dn x) (div a.dn x))
?: s ~
['.' ((d-co:co m) (mod a.dn x))]
--

14332
desk/lib/sigil/symbols.hoon Normal file

File diff suppressed because it is too large Load Diff

51
desk/lib/skeleton.hoon Normal file
View File

@ -0,0 +1,51 @@
:: Similar to default-agent except crashes everywhere
^- agent:gall
|_ bowl:gall
++ on-init
^- (quip card:agent:gall agent:gall)
!!
::
++ on-save
^- vase
!!
::
++ on-load
|~ old-state=vase
^- (quip card:agent:gall agent:gall)
!!
::
++ on-poke
|~ in-poke-data=cage
^- (quip card:agent:gall agent:gall)
!!
::
++ on-watch
|~ path
^- (quip card:agent:gall agent:gall)
!!
::
++ on-leave
|~ path
^- (quip card:agent:gall agent:gall)
!!
::
++ on-peek
|~ path
^- (unit (unit cage))
!!
::
++ on-agent
|~ [wire sign:agent:gall]
^- (quip card:agent:gall agent:gall)
!!
::
++ on-arvo
|~ [wire =sign-arvo]
^- (quip card:agent:gall agent:gall)
!!
::
++ on-fail
|~ [term tang]
^- (quip card:agent:gall agent:gall)
!!
--

1
desk/lib/sortug.hoon Symbolic link
View File

@ -0,0 +1 @@
/home/y/code/urbit/hoonlib/sortug.hoon

388
desk/lib/sss.hoon Normal file
View File

@ -0,0 +1,388 @@
::
:: XX remove this file in transition to %4
::
/- *sss
/+ *mip
::
|%
++ mk-subs :: Create sub-map.
|* [=(lake) paths=mold]
-:+6:(da lake paths)
::
++ mk-pubs :: Create pub-map.
|* [=(lake) paths=mold]
-:+6:(du lake paths)
::
++ mk-mar :: Create mar.
|* =(lake)
|_ =(response:poke lake *)
++ grow
|%
++ noun response
--
++ grab
|%
++ noun (response:poke lake *)
--
++ grad %noun
--
++ fled :: Like +sped but head is a path.
|= vax=vase
^- vase
:_ q.vax
%- ~(play ut p.vax)
=- [%wtgr [%wtts - [%& 2]~] [%$ 1]]
=/ pax ~| %path-none ;;(path -.q.vax)
|- ^- spec
?~ pax [%base %null]
[%bccl ~[[%leaf %ta -.pax] $(pax +.pax)]]
::
++ zoom |= =noun ~| %need-path $/sss/;;(path noun)
::
++ da :: Manage subscriptions.
|* [=(lake) paths=mold]
=>
|%
+$ from (on-rock:poke lake paths)
+$ into (response:poke lake paths)
+$ result (request:poke paths)
+$ fail [paths ship dude]
+$ flow [=aeon stale=_| fail=_| =rock:lake]
+$ subs [%0 (map [ship dude paths] (unit flow))]
--
|= $: sub=subs
=bowl:gall
result-type=type
on-rock-type=type
fail-type=type
==
=> .(sub +.sub)
|%
++ surf :: Subscribe to [ship dude path].
|= which=[ship dude paths]
^- (quip card:agent:gall subs)
?+ flow=(~(get by sub) which) `0/sub
~ [~[(pine which)] 0/(~(put by sub) which ~)]
[~ ~] [~[(pine which)] 0/sub]
[~ ~ [* %& * *]] [~[(scry `+(aeon.u.u.flow) which)] 0/sub]
==
++ quit (corl (lead %0) ~(del by sub)) :: Unsub from [ship dude path].
++ read :: See current subscribed states.
^- (map [ship dude paths] [stale=? fail=? =rock:lake])
%- malt %+ murn ~(tap by sub)
|= [key=[ship dude paths] val=(unit flow)]
?~ val ~
`[key +.u.val]
:: :: Check poke-ack for errors.
:: :: If an %sss-on-rock poke nacks,
++ chit :: that state is flagged as failed.
|= [[aeon=term ship=term dude=term path=paths] =sign:agent:gall]
^- subs
:- %0
?> ?=(%poke-ack -.sign)
?~ p.sign sub
%+ ~(jab by sub) [(slav %p ship) dude path]
|= (unit flow)
=/ =flow (need +<)
?> =(aeon.flow (slav %ud aeon))
`flow(fail &)
:: :: Check poke-ack for errors.
:: :: If a scry request nacks,
++ tell :: that state is flagged as stale.
|= [[ship=term =dude aeon=term path=paths] =sign:agent:gall]
^- (quip card:agent:gall subs)
?> ?=(%poke-ack -.sign)
?~ p.sign `0/sub
=/ current [ship=(slav %p ship) dude=dude path=path]
?+ flow=(~(get by sub) current) `0/sub
[~ ~ *]
=. stale.u.u.flow &
:_ 0/(~(put by sub) current u.flow)
~[(on-rock-poke current u.u.flow ~)]
::
[~ ~]
:_ 0/(~(del by sub) current) :_ ~
:* %pass (zoom surf-fail/aeon/ship/dude/path)
%agent [our dap]:bowl
%poke %sss-surf-fail fail-type ^- fail
[path ship dude]:current
==
==
:: :: Check if we're still interested
:: :: in a wave. If no, no-op.
:: :: If yes, scry.
++ behn :: (See https://gist.github.com/belisarius222/7f8452bfea9b199c0ed717ab1778f35b)
|= [ship=term =dude aeon=term path=paths]
^- (list card:agent:gall)
%- fall :_ ~ %- mole |.
=/ ship (slav %p ship)
=/ aeon (slav %ud aeon)
?: (lte aeon aeon:(fall (~(got by sub) ship dude path) *flow)) ~
~[(scry `aeon ship dude path)]
::
++ apply :: Handle response from publisher.
|= res=(response:poke lake paths)
^- (quip card:agent:gall subs)
%- fall :_ `0/sub %- mole |.
=* current [src.bowl dude.res path.res]
=/ old=flow (fall (~(got by sub) current) *flow)
?- type.res
%tomb
=/ =flow old(stale &)
:_ 0/(~(put by sub) current `flow) :_ ~
(on-rock-poke current flow ~)
::
%yore
:_ 0/sub :_ ~
(pine src.bowl dude.res path.res)
::
%nigh
:_ 0/sub :_ ~
(behn-s25 [dude aeon path]:res)
::
%scry
=/ [wave=(unit wave:lake) =flow]
?- what.res
%rock ?> (gte aeon.res aeon.old)
[~ [aeon.res | | rock.res]]
%wave ?> =(aeon.res +(aeon.old))
[`wave.res [aeon.res | | (wash:lake rock.old wave.res)]]
==
:_ 0/(~(put by sub) current `flow)
:~ (on-rock-poke current flow wave)
(scry `+(aeon.res) src.bowl dude.res path.res)
==
==
::
:: Non-public facing arms below
::
++ behn-s25
|= [=dude =aeon path=noun]
^- card:agent:gall
:* %pass (zoom behn/(scot %p src.bowl)^dude^(scot %ud aeon)^path)
%arvo %b %wait (add ~s25 now.bowl)
==
++ pine |= [ship dude paths] (scry ~ +<)
++ scry
|= [when=(unit aeon) who=ship which=dude where=paths]
^- card:agent:gall
=/ when ?~ when ~ (scot %ud u.when)
:* %pass (zoom scry-request/(scot %p who)^which^when^where)
%agent [who which]
%poke %sss-to-pub :- result-type ^- result
[where dap.bowl ^when]
==
++ on-rock-poke
|= [[=ship =dude path=paths] flow wave=(unit wave:lake)]
^- card:agent:gall
:* %pass (zoom on-rock/(scot %ud aeon)^(scot %p ship)^dude^path)
%agent [our dap]:bowl
%poke %sss-on-rock on-rock-type ^- from
[path ship dude stale fail rock wave]
==
--
++ du :: Manage publications.
|* [=(lake) paths=mold]
=>
|%
+$ into (request:poke paths)
+$ result (response:poke lake paths)
+$ rule [rocks=_1 waves=_5] :: Retention policy
+$ tide
$: rok=((mop aeon rock:lake) gte)
wav=((mop aeon wave:lake) lte)
rul=rule
mem=(mip ship dude @da)
==
+$ buoy
$: tid=$~(*tide $@(aeon tide))
alo=(unit (set ship))
==
+$ pubs [%0 (map paths buoy)]
--
|= [pub=pubs =bowl:gall result-type=type]
=> .(pub +.pub)
=* rok ((on aeon rock:lake) gte)
=* wav ((on aeon wave:lake) lte)
|%
++ rule :: Set new retention policy.
|= [path=paths =^rule]
^- pubs
:- %0
%+ ~(jab by pub) path
|= =buoy
?@ tid.buoy buoy
buoy(tid (form tid.buoy(rul rule)))
::
++ wipe :: Create new rock and wipe rest.
|= path=paths
^- pubs
:- %0
%+ ~(jab by pub) path
|= =buoy
?@ tid.buoy buoy
%* . buoy(tid (form tid.buoy(rul [0 1])))
rul.tid rul.tid.buoy
wav.tid ~
==
++ give :: Give a wave on a path.
|= [path=paths =wave:lake]
^- (quip card:agent:gall pubs)
?~ ((soft ^path) path) ~| %need-path !!
=/ buoy (~(gut by pub) path *buoy)
=? buoy ?=(@ tid.buoy)
%*(. buoy(tid *tide) rok.tid (put:rok ~ +(tid.buoy) *rock:lake))
?> ?=(^ tid.buoy)
=* tide tid.buoy
=/ next=aeon +((latest tide))
:- %+ murn ~(tap bi mem.tide)
|= [=ship =dude =@da]
?: (lth da now.bowl) ~
`(send scry/wave/wave ship dude next path)
:- %0
%+ ~(put by pub) path
=/ last=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
=. wav.tide (put:wav wav.tide next wave)
=. mem.tide ~
?. =(next (add aeon.last waves.rul.tide)) buoy
buoy(tid (form tide))
::
++ fork :: Fork a pub into an empty path.
|= [from=paths to=paths]
^- pubs
:- %0
?< (~(has by pub) to)
(~(put by pub) to (~(got by pub) from))
::
++ copy :: Fork a sub into an empty path.
|= [sub=_(mk-subs lake *) from=[ship dude *] to=paths]
^- pubs
:- %0
?< (~(has by pub) to)
%+ ~(put by pub) to
%* . *$<(aeon buoy)
rok.tid (put:rok ~ [aeon rock]:(need (~(got by +:sub) from)))
==
::
++ perm :: Change permissions with gate.
|= [where=(list paths) diff=$-((unit (set ship)) (unit (set ship)))]
^- pubs
%+ edit where
|= =buoy
=/ new=_alo.buoy (diff alo.buoy)
?@ tid.buoy buoy(alo new)
%= buoy
alo new
mem.tid ?~ new mem.tid.buoy
%. mem.tid.buoy
~(int by (malt (turn ~(tap in u.new) (late *(map @ @)))))
==
++ public (curr perm _~) :: Make list of paths public.
++ secret (curr perm _`~) :: Make list of paths secret.
:: :: Block ships from paths.
++ block :: No-ops on public paths.
|= [who=(list ship) whence=(list paths)]
^- pubs
%+ perm whence
|= old=(unit (set ship))
?~ old ~ `(~(dif in u.old) (sy who))
:: :: Allow ships to paths.
++ allow :: Any public paths will no-op.
|= [who=(list ship) where=(list paths)]
^- pubs
%+ perm where
|= old=(unit (set ship))
?~ old ~ `(~(gas in u.old) who)
:: :: Kill a list of paths, i.e. tell
++ kill :: subs to not expect updates.
(curr edit |=(=buoy buoy(tid (latest tid.buoy))))
:: :: Reopen list of killed paths.
++ read :: See current published states.
^- (map paths [allowed=(unit (set ship)) =rock:lake])
%- malt %+ murn ~(tap by pub)
|= [path=paths =buoy]
^- (unit [paths (unit (set ship)) rock:lake])
?@ tide=tid.buoy ~
:^ ~ path alo.buoy =< rock
=/ snap=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
%+ roll (tap:wav (lot:wav wav.tide `aeon.snap ~))
|= [[=aeon =wave:lake] =_snap]
?. =(aeon +(aeon.snap)) snap
[aeon (wash:lake rock.snap wave)]
::
++ apply :: Handle request from subscriber.
|= req=(request:poke paths)
^- (quip card:agent:gall pubs)
=/ =buoy (~(gut by pub) path.req *buoy)
?< &(?=(^ alo.buoy) !(~(has in u.alo.buoy) src.bowl))
?@ tid.buoy
:_ 0/pub :_ ~
(send tomb/~ src.bowl dude.req tid.buoy path.req)
?~ when.req
=/ last (fall (pry:rok rok.tid.buoy) *[=key =val]:rok)
:_ 0/pub :_ ~
(send scry/rock/val.last src.bowl dude.req key.last path.req)
?^ dat=(get:wav wav.tid.buoy u.when.req)
:_ 0/pub :_ ~
(send scry/wave/u.dat src.bowl [dude u.when path]:req)
?: %+ lte u.when.req
key::(fall (ram:wav wav.tid.buoy) (pry:rok rok.tid.buoy) [=key val]:wav)
:_ 0/pub :_ ~
(send yore/~ src.bowl [dude u.when path]:req)
?> =(u.when.req +((latest tid.buoy)))
:- ~[(send nigh/~ src.bowl [dude u.when path]:req)]
:- %0
%+ ~(put by pub) path.req
%= buoy
mem.tid (~(put bi mem.tid.buoy) src.bowl dude.req (add ~s25 now.bowl))
==
::
:: Non-public facing arms below
::
++ send
|= [payload=_|3:*(response:poke lake paths) =ship =dude =aeon path=paths]
^- card:agent:gall
=* mark (cat 3 %sss- name:lake)
:* %pass (zoom scry-response/(scot %p ship)^dude^(scot %ud aeon)^path)
%agent [ship dude]
%poke mark result-type ^- (response:poke lake paths)
[path dap.bowl aeon payload]
==
++ latest
|= =$@(aeon tide)
^- aeon
?@ tide tide
%+ max (fall (bind (pry:rok rok.tide) head) 0)
(fall (bind (ram:wav wav.tide) head) 0)
::
++ edit
|= [ps=(list paths) edit=$-(buoy buoy)]
^- pubs
:- %0
%- ~(rep in (sy ps))
|= [path=paths =_pub]
%- fall :_ pub %- mole |.
(~(jab by pub) path edit)
::
++ form
|= =tide
^+ tide
=/ max-rock=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
=/ max-wave (fall (bind (ram:wav wav.tide) head) 0)
=. rok.tide
%+ gas:rok +<-:gas:rok
%- tab:rok :_ [~ +(rocks.rul.tide)]
?: ?| =(waves.rul.tide 0)
(lth max-wave (add aeon.max-rock waves.rul.tide))
==
rok.tide
%+ put:rok rok.tide
%+ roll (tab:wav wav.tide `aeon.max-rock max-wave)
|: [*[now=aeon =wave:lake] `[prev=aeon =rock:lake]`max-rock]
~| %aeon-awry
?> =(now +(prev))
[now (wash:lake rock wave)]
~| %rock-zero
tide(wav (lot:wav wav.tide (bind (ram:rok rok.tide) |=([r=@ *] (dec r))) ~))
--
--

1
desk/lib/strand.hoon Normal file
View File

@ -0,0 +1 @@
rand

832
desk/lib/strandio.hoon Normal file
View File

@ -0,0 +1,832 @@
/- spider
/+ libstrand=strand
=, strand=strand:libstrand
=, strand-fail=strand-fail:libstrand
|%
++ send-raw-cards
|= cards=(list =card:agent:gall)
=/ m (strand ,~)
^- form:m
|= strand-input:strand
[cards %done ~]
::
++ send-raw-card
|= =card:agent:gall
=/ m (strand ,~)
^- form:m
(send-raw-cards card ~)
::
++ ignore
|= tin=strand-input:strand
`[%fail %ignore ~]
::
++ get-bowl
=/ m (strand ,bowl:strand)
^- form:m
|= tin=strand-input:strand
`[%done bowl.tin]
::
++ get-beak
=/ m (strand ,beak)
^- form:m
|= tin=strand-input:strand
`[%done [our q.byk da+now]:bowl.tin]
::
++ get-time
=/ m (strand ,@da)
^- form:m
|= tin=strand-input:strand
`[%done now.bowl.tin]
::
++ get-our
=/ m (strand ,ship)
^- form:m
|= tin=strand-input:strand
`[%done our.bowl.tin]
::
++ get-entropy
=/ m (strand ,@uvJ)
^- form:m
|= tin=strand-input:strand
`[%done eny.bowl.tin]
::
:: Convert skips to %ignore failures.
::
:: This tells the main loop to try the next handler.
::
++ handle
|* a=mold
=/ m (strand ,a)
|= =form:m
^- form:m
|= tin=strand-input:strand
=/ res (form tin)
=? next.res ?=(%skip -.next.res)
[%fail %ignore ~]
res
::
:: Wait for a poke with a particular mark
::
++ take-poke
|= =mark
=/ m (strand ,vase)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~
`[%wait ~]
::
[~ %poke @ *]
?. =(mark p.cage.u.in.tin)
`[%skip ~]
`[%done q.cage.u.in.tin]
==
::
++ take-sign-arvo
=/ m (strand ,[wire sign-arvo])
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~
`[%wait ~]
::
[~ %sign *]
`[%done [wire sign-arvo]:u.in.tin]
==
::
:: Wait for a subscription update on a wire
::
++ take-fact-prefix
|= =wire
=/ m (strand ,[path cage])
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %agent * %fact *]
?. =(watch+wire (scag +((lent wire)) wire.u.in.tin))
`[%skip ~]
`[%done (slag (lent wire) wire.u.in.tin) cage.sign.u.in.tin]
==
::
:: Wait for a subscription update on a wire
::
++ take-fact
|= =wire
=/ m (strand ,cage)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %agent * %fact *]
?. =(watch+wire wire.u.in.tin)
`[%skip ~]
`[%done cage.sign.u.in.tin]
==
::
:: Wait for a subscription close
::
++ take-kick
|= =wire
=/ m (strand ,~)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %agent * %kick *]
?. =(watch+wire wire.u.in.tin)
`[%skip ~]
`[%done ~]
==
::
++ echo
=/ m (strand ,~)
^- form:m
%- (main-loop ,~)
:~ |= ~
^- form:m
;< =vase bind:m ((handle ,vase) (take-poke %echo))
=/ message=tape !<(tape vase)
%- (slog leaf+"{message}..." ~)
;< ~ bind:m (sleep ~s2)
%- (slog leaf+"{message}.." ~)
(pure:m ~)
::
|= ~
^- form:m
;< =vase bind:m ((handle ,vase) (take-poke %over))
%- (slog leaf+"over..." ~)
(pure:m ~)
==
::
++ take-watch
=/ m (strand ,path)
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %watch *]
`[%done path.u.in.tin]
==
::
++ take-wake
|= until=(unit @da)
=/ m (strand ,~)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %sign [%wait @ ~] %behn %wake *]
?. |(?=(~ until) =(`u.until (slaw %da i.t.wire.u.in.tin)))
`[%skip ~]
?~ error.sign-arvo.u.in.tin
`[%done ~]
`[%fail %timer-error u.error.sign-arvo.u.in.tin]
==
::
++ take-tune
|= =wire
=/ m (strand ,[spar:ames (unit roar:ames)])
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
::
[~ %sign * %ames %tune ^ *]
?. =(wire wire.u.in.tin)
`[%skip ~]
`[%done +>.sign-arvo.u.in.tin]
==
::
++ take-near
|= =wire
=/ m (strand ,[spar:ames (unit (unit page))])
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
::
[~ %sign * %ames %near ^ *]
?. =(wire wire.u.in.tin)
`[%skip ~]
`[%done +>.sign-arvo.u.in.tin]
==
::
++ take-poke-ack
|= =wire
=/ m (strand ,~)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %agent * %poke-ack *]
?. =(wire wire.u.in.tin)
`[%skip ~]
?~ p.sign.u.in.tin
`[%done ~]
`[%fail %poke-fail u.p.sign.u.in.tin]
==
::
++ take-watch-ack
|= =wire
=/ m (strand ,~)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %agent * %watch-ack *]
?. =(watch+wire wire.u.in.tin)
`[%skip ~]
?~ p.sign.u.in.tin
`[%done ~]
`[%fail %watch-ack-fail u.p.sign.u.in.tin]
==
::
++ poke
|= [=dock =cage]
=/ m (strand ,~)
^- form:m
=/ =card:agent:gall [%pass /poke %agent dock %poke cage]
;< ~ bind:m (send-raw-card card)
(take-poke-ack /poke)
::
++ raw-poke
|= [=dock =cage]
=/ m (strand ,~)
^- form:m
=/ =card:agent:gall [%pass /poke %agent dock %poke cage]
;< ~ bind:m (send-raw-card card)
=/ m (strand ,~)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~
`[%wait ~]
::
[~ %agent * %poke-ack *]
?. =(/poke wire.u.in.tin)
`[%skip ~]
`[%done ~]
==
::
++ raw-poke-our
|= [app=term =cage]
=/ m (strand ,~)
^- form:m
;< =bowl:spider bind:m get-bowl
(raw-poke [our.bowl app] cage)
::
++ poke-our
|= [=term =cage]
=/ m (strand ,~)
^- form:m
;< our=@p bind:m get-our
(poke [our term] cage)
::
++ watch
|= [=wire =dock =path]
=/ m (strand ,~)
^- form:m
=/ =card:agent:gall [%pass watch+wire %agent dock %watch path]
;< ~ bind:m (send-raw-card card)
(take-watch-ack wire)
::
++ watch-one
|= [=wire =dock =path]
=/ m (strand ,cage)
^- form:m
;< ~ bind:m (watch wire dock path)
;< =cage bind:m (take-fact wire)
;< ~ bind:m (take-kick wire)
(pure:m cage)
::
++ watch-our
|= [=wire =term =path]
=/ m (strand ,~)
^- form:m
;< our=@p bind:m get-our
(watch wire [our term] path)
::
++ scry
|* [=mold =path]
=/ m (strand ,mold)
^- form:m
?> ?=(^ path)
?> ?=(^ t.path)
;< =bowl:spider bind:m get-bowl
%- pure:m
.^(mold i.path (scot %p our.bowl) i.t.path (scot %da now.bowl) t.t.path)
::
++ leave
|= [=wire =dock]
=/ m (strand ,~)
^- form:m
=/ =card:agent:gall [%pass watch+wire %agent dock %leave ~]
(send-raw-card card)
::
++ leave-our
|= [=wire =term]
=/ m (strand ,~)
^- form:m
;< our=@p bind:m get-our
(leave wire [our term])
::
++ rewatch
|= [=wire =dock =path]
=/ m (strand ,~)
;< ~ bind:m ((handle ,~) (take-kick wire))
;< ~ bind:m (flog-text "rewatching {<dock>} {<path>}")
;< ~ bind:m (watch wire dock path)
(pure:m ~)
::
++ wait
|= until=@da
=/ m (strand ,~)
^- form:m
;< ~ bind:m (send-wait until)
(take-wake `until)
::
++ keen
|= [=wire =spar:ames]
=/ m (strand ,~)
^- form:m
(send-raw-card %pass wire %arvo %a %keen ~ spar)
::
++ keen-shut
|= [=wire =spar:ames]
=/ m (strand ,~)
^- form:m
(send-raw-card %pass wire %keen & spar)
::
++ sleep
|= for=@dr
=/ m (strand ,~)
^- form:m
;< now=@da bind:m get-time
(wait (add now for))
::
++ send-wait
|= until=@da
=/ m (strand ,~)
^- form:m
=/ =card:agent:gall
[%pass /wait/(scot %da until) %arvo %b %wait until]
(send-raw-card card)
::
++ map-err
|* computation-result=mold
=/ m (strand ,computation-result)
|= [f=$-([term tang] [term tang]) computation=form:m]
^- form:m
|= tin=strand-input:strand
=* loop $
=/ c-res (computation tin)
?: ?=(%cont -.next.c-res)
c-res(self.next ..loop(computation self.next.c-res))
?. ?=(%fail -.next.c-res)
c-res
c-res(err.next (f err.next.c-res))
::
++ set-timeout
|* computation-result=mold
=/ m (strand ,computation-result)
|= [time=@dr computation=form:m]
^- form:m
;< now=@da bind:m get-time
=/ when (add now time)
=/ =card:agent:gall
[%pass /timeout/(scot %da when) %arvo %b %wait when]
;< ~ bind:m (send-raw-card card)
|= tin=strand-input:strand
=* loop $
?: ?& ?=([~ %sign [%timeout @ ~] %behn %wake *] in.tin)
=((scot %da when) i.t.wire.u.in.tin)
==
`[%fail %timeout ~]
=/ c-res (computation tin)
?: ?=(%cont -.next.c-res)
c-res(self.next ..loop(computation self.next.c-res))
?: ?=(%done -.next.c-res)
=/ =card:agent:gall
[%pass /timeout/(scot %da when) %arvo %b %rest when]
c-res(cards [card cards.c-res])
c-res
::
++ send-request
|= =request:http
=/ m (strand ,~)
^- form:m
(send-raw-card %pass /request %arvo %i %request request *outbound-config:iris)
::
++ send-cancel-request
=/ m (strand ,~)
^- form:m
(send-raw-card %pass /request %arvo %i %cancel-request ~)
::
++ take-client-response
=/ m (strand ,client-response:iris)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
::
[~ %sign [%request ~] %iris %http-response %cancel *]
::NOTE iris does not (yet?) retry after cancel, so it means failure
:- ~
:+ %fail
%http-request-cancelled
['http request was cancelled by the runtime']~
::
[~ %sign [%request ~] %iris %http-response %finished *]
`[%done client-response.sign-arvo.u.in.tin]
==
::
:: Wait until we get an HTTP response or cancelation and unset contract
::
++ take-maybe-sigh
=/ m (strand ,(unit httr:eyre))
^- form:m
;< rep=(unit client-response:iris) bind:m
take-maybe-response
?~ rep
(pure:m ~)
:: XX s/b impossible
::
?. ?=(%finished -.u.rep)
(pure:m ~)
(pure:m (some (to-httr:iris +.u.rep)))
::
++ take-maybe-response
=/ m (strand ,(unit client-response:iris))
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %sign [%request ~] %iris %http-response %cancel *]
`[%done ~]
[~ %sign [%request ~] %iris %http-response %finished *]
`[%done `client-response.sign-arvo.u.in.tin]
==
::
++ extract-body
|= =client-response:iris
=/ m (strand ,cord)
^- form:m
?> ?=(%finished -.client-response)
%- pure:m
?~ full-file.client-response ''
q.data.u.full-file.client-response
::
++ fetch-cord
|= url=tape
=/ m (strand ,cord)
^- form:m
=/ =request:http [%'GET' (crip url) ~ ~]
;< ~ bind:m (send-request request)
;< =client-response:iris bind:m take-client-response
(extract-body client-response)
::
++ fetch-json
|= url=tape
=/ m (strand ,json)
^- form:m
;< =cord bind:m (fetch-cord url)
=/ json=(unit json) (de:json:html cord)
?~ json
(strand-fail %json-parse-error ~)
(pure:m u.json)
::
++ hiss-request
|= =hiss:eyre
=/ m (strand ,(unit httr:eyre))
^- form:m
;< ~ bind:m (send-request (hiss-to-request:html hiss))
take-maybe-sigh
::
:: +build-file: build the source file at the specified $beam
::
++ build-file
|= [[=ship =desk =case] =spur]
=* arg +<
=/ m (strand ,(unit vase))
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %a case spur)
?~ riot
(pure:m ~)
?> =(%vase p.r.u.riot)
(pure:m (some !<(vase q.r.u.riot)))
::
++ build-file-hard
|= [[=ship =desk =case] =spur]
=* arg +<
=/ m (strand ,vase)
^- form:m
;< =riot:clay
bind:m
(warp ship desk ~ %sing %a case spur)
?> ?=(^ riot)
?> ?=(%vase p.r.u.riot)
(pure:m !<(vase q.r.u.riot))
:: +build-mark: build a mark definition to a $dais
::
++ build-mark
|= [[=ship =desk =case] mak=mark]
=* arg +<
=/ m (strand ,dais:clay)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %b case /[mak])
?~ riot
(strand-fail %build-mark >arg< ~)
?> =(%dais p.r.u.riot)
(pure:m !<(dais:clay q.r.u.riot))
:: +build-tube: build a mark conversion gate ($tube)
::
++ build-tube
|= [[=ship =desk =case] =mars:clay]
=* arg +<
=/ m (strand ,tube:clay)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %c case /[a.mars]/[b.mars])
?~ riot
(strand-fail %build-tube >arg< ~)
?> =(%tube p.r.u.riot)
(pure:m !<(tube:clay q.r.u.riot))
::
:: +build-nave: build a mark definition to a $nave
::
++ build-nave
|= [[=ship =desk =case] mak=mark]
=* arg +<
=/ m (strand ,vase)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %e case /[mak])
?~ riot
(strand-fail %build-nave >arg< ~)
?> =(%nave p.r.u.riot)
(pure:m q.r.u.riot)
:: +build-cast: build a mark conversion gate (static)
::
++ build-cast
|= [[=ship =desk =case] =mars:clay]
=* arg +<
=/ m (strand ,vase)
^- form:m
;< =riot:clay bind:m
(warp ship desk ~ %sing %f case /[a.mars]/[b.mars])
?~ riot
(strand-fail %build-cast >arg< ~)
?> =(%cast p.r.u.riot)
(pure:m q.r.u.riot)
::
:: Read from Clay
::
++ warp
|= [=ship =riff:clay]
=/ m (strand ,riot:clay)
;< ~ bind:m (send-raw-card %pass /warp %arvo %c %warp ship riff)
(take-writ /warp)
::
++ read-file
|= [[=ship =desk =case] =spur]
=* arg +<
=/ m (strand ,cage)
;< =riot:clay bind:m (warp ship desk ~ %sing %x case spur)
?~ riot
(strand-fail %read-file >arg< ~)
(pure:m r.u.riot)
::
++ check-for-file
|= [[=ship =desk =case] =spur]
=/ m (strand ,?)
;< =riot:clay bind:m (warp ship desk ~ %sing %u case spur)
?> ?=(^ riot)
(pure:m !<(? q.r.u.riot))
::
++ list-tree
|= [[=ship =desk =case] =spur]
=* arg +<
=/ m (strand ,(list path))
;< =riot:clay bind:m (warp ship desk ~ %sing %t case spur)
?~ riot
(strand-fail %list-tree >arg< ~)
(pure:m !<((list path) q.r.u.riot))
::
:: Take Clay read result
::
++ take-writ
|= =wire
=/ m (strand ,riot:clay)
^- form:m
|= tin=strand-input:strand
?+ in.tin `[%skip ~]
~ `[%wait ~]
[~ %sign * ?(%behn %clay) %writ *]
?. =(wire wire.u.in.tin)
`[%skip ~]
`[%done +>.sign-arvo.u.in.tin]
==
:: +check-online: require that peer respond before timeout
::
++ check-online
|= [who=ship lag=@dr]
=/ m (strand ,~)
^- form:m
%+ (map-err ,~) |=(* [%offline *tang])
%+ (set-timeout ,~) lag
;< ~ bind:m
(poke [who %hood] %helm-hi !>(~))
(pure:m ~)
::
++ eval-hoon
|= [gen=hoon bez=(list beam)]
=/ m (strand ,vase)
^- form:m
=/ sut=vase !>(..zuse)
|-
?~ bez
(pure:m (slap sut gen))
;< vax=vase bind:m (build-file-hard i.bez)
$(bez t.bez, sut (slop vax sut))
::
++ send-thread
|= [=bear:khan =shed:khan =wire]
=/ m (strand ,~)
^- form:m
(send-raw-card %pass wire %arvo %k %lard bear shed)
::
:: Queue on skip, try next on fail %ignore
::
++ main-loop
|* a=mold
=/ m (strand ,~)
=/ m-a (strand ,a)
=| queue=(qeu (unit input:strand))
=| active=(unit [in=(unit input:strand) =form:m-a forms=(list $-(a form:m-a))])
=| state=a
|= forms=(lest $-(a form:m-a))
^- form:m
|= tin=strand-input:strand
=* top `form:m`..$
=. queue (~(put to queue) in.tin)
|^ (continue bowl.tin)
::
++ continue
|= =bowl:strand
^- output:m
?> =(~ active)
?: =(~ queue)
`[%cont top]
=^ in=(unit input:strand) queue ~(get to queue)
^- output:m
=. active `[in (i.forms state) t.forms]
^- output:m
(run bowl in)
::
++ run
^- form:m
|= tin=strand-input:strand
^- output:m
?> ?=(^ active)
=/ res (form.u.active tin)
=/ =output:m
?- -.next.res
%wait `[%wait ~]
%skip `[%cont ..$(queue (~(put to queue) in.tin))]
%cont `[%cont ..$(active `[in.u.active self.next.res forms.u.active])]
%done (continue(active ~, state value.next.res) bowl.tin)
%fail
?: &(?=(^ forms.u.active) ?=(%ignore p.err.next.res))
%= $
active `[in.u.active (i.forms.u.active state) t.forms.u.active]
in.tin in.u.active
==
`[%fail err.next.res]
==
[(weld cards.res cards.output) next.output]
--
::
++ retry
|* result=mold
|= [crash-after=(unit @ud) computation=_*form:(strand (unit result))]
=/ m (strand ,result)
=| try=@ud
|- ^- form:m
=* loop $
?: =(crash-after `try)
(strand-fail %retry-too-many ~)
;< ~ bind:m (backoff try ~m1)
;< res=(unit result) bind:m computation
?^ res
(pure:m u.res)
loop(try +(try))
::
++ backoff
|= [try=@ud limit=@dr]
=/ m (strand ,~)
^- form:m
;< eny=@uvJ bind:m get-entropy
%- sleep
%+ min limit
?: =(0 try) ~s0
%+ add
(mul ~s1 (bex (dec try)))
(mul ~s0..0001 (~(rad og eny) 1.000))
::
:: ----
::
:: Output
::
++ flog
|= =flog:dill
=/ m (strand ,~)
^- form:m
(send-raw-card %pass / %arvo %d %flog flog)
::
++ flog-text
|= =tape
=/ m (strand ,~)
^- form:m
(flog %text tape)
::
++ flog-tang
|= =tang
=/ m (strand ,~)
^- form:m
=/ =wall
(zing (turn (flop tang) (cury wash [0 80])))
|- ^- form:m
=* loop $
?~ wall
(pure:m ~)
;< ~ bind:m (flog-text i.wall)
loop(wall t.wall)
::
++ trace
|= =tang
=/ m (strand ,~)
^- form:m
(pure:m ((slog tang) ~))
::
++ app-message
|= [app=term =cord =tang]
=/ m (strand ,~)
^- form:m
=/ msg=tape :(weld (trip app) ": " (trip cord))
;< ~ bind:m (flog-text msg)
(flog-tang tang)
::
:: ----
::
:: Handle domains
::
++ install-domain
|= =turf
=/ m (strand ,~)
^- form:m
(send-raw-card %pass / %arvo %e %rule %turf %put turf)
::
:: ----
::
:: Threads
::
++ start-thread
|= file=term
=/ m (strand ,tid:spider)
;< =bowl:spider bind:m get-bowl
(start-thread-with-args byk.bowl file *vase)
::
++ start-thread-with-args
|= [=beak file=term args=vase]
=/ m (strand ,tid:spider)
^- form:m
;< =bowl:spider bind:m get-bowl
=/ tid
(scot %ta (cat 3 (cat 3 'strand_' file) (scot %uv (sham file eny.bowl))))
=/ poke-vase !>(`start-args:spider`[`tid.bowl `tid beak file args])
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
;< ~ bind:m (sleep ~s0) :: wait for thread to start
(pure:m tid)
::
+$ thread-result
(each vase [term tang])
::
++ await-thread
|= [file=term args=vase]
=/ m (strand ,thread-result)
^- form:m
;< =bowl:spider bind:m get-bowl
=/ tid (scot %ta (cat 3 'strand_' (scot %uv (sham file eny.bowl))))
=/ poke-vase !>(`start-args:spider`[`tid.bowl `tid byk.bowl file args])
;< ~ bind:m (watch-our /awaiting/[tid] %spider /thread-result/[tid])
;< ~ bind:m (poke-our %spider %spider-start poke-vase)
;< ~ bind:m (sleep ~s0) :: wait for thread to start
;< =cage bind:m (take-fact /awaiting/[tid])
;< ~ bind:m (take-kick /awaiting/[tid])
?+ p.cage ~|([%strange-thread-result p.cage file tid] !!)
%thread-done (pure:m %& q.cage)
%thread-fail (pure:m %| ;;([term tang] q.q.cage))
==
--

670
desk/lib/tiny.hoon Normal file
View File

@ -0,0 +1,670 @@
!.
=> %a50
~% %a.50 ~ ~
|%
:: Types
::
+$ ship @p
+$ life @ud
+$ rift @ud
+$ pass @
+$ bloq @
+$ step _`@u`1
+$ bite $@(bloq [=bloq =step])
+$ octs [p=@ud q=@]
+$ mold $~(* $-(* *))
++ unit |$ [item] $@(~ [~ u=item])
++ list |$ [item] $@(~ [i=item t=(list item)])
++ lest |$ [item] [i=item t=(list item)]
++ tree |$ [node] $@(~ [n=node l=(tree node) r=(tree node)])
++ pair |$ [head tail] [p=head q=tail]
++ map
|$ [key value]
$| (tree (pair key value))
|=(a=(tree (pair)) ?:(=(~ a) & ~(apt by a)))
::
++ set
|$ [item]
$| (tree item)
|=(a=(tree) ?:(=(~ a) & ~(apt in a)))
::
++ jug |$ [key value] (map key (set value))
::
:: Bits
::
++ dec :: decrement
~/ %dec
|= a=@
~_ leaf+"decrement-underflow"
?< =(0 a)
=+ b=0
|- ^- @
?: =(a +(b)) b
$(b +(b))
::
++ add :: plus
~/ %add
|= [a=@ b=@]
^- @
?: =(0 a) b
$(a (dec a), b +(b))
::
++ sub :: subtract
~/ %sub
|= [a=@ b=@]
~_ leaf+"subtract-underflow"
:: difference
^- @
?: =(0 b) a
$(a (dec a), b (dec b))
::
++ mul :: multiply
~/ %mul
|: [a=`@`1 b=`@`1]
^- @
=+ c=0
|-
?: =(0 a) c
$(a (dec a), c (add b c))
::
++ div :: divide
~/ %div
|: [a=`@`1 b=`@`1]
^- @
~_ leaf+"divide-by-zero"
?< =(0 b)
=+ c=0
|-
?: (lth a b) c
$(a (sub a b), c +(c))
::
++ dvr :: divide w/remainder
~/ %dvr
|: [a=`@`1 b=`@`1]
^- [p=@ q=@]
[(div a b) (mod a b)]
::
++ mod :: modulus
~/ %mod
|: [a=`@`1 b=`@`1]
^- @
?< =(0 b)
(sub a (mul b (div a b)))
::
++ bex :: binary exponent
~/ %bex
|= a=bloq
^- @
?: =(0 a) 1
(mul 2 $(a (dec a)))
::
++ lsh :: left-shift
~/ %lsh
|= [a=bite b=@]
=/ [=bloq =step] ?^(a a [a *step])
(mul b (bex (mul (bex bloq) step)))
::
++ rsh :: right-shift
~/ %rsh
|= [a=bite b=@]
=/ [=bloq =step] ?^(a a [a *step])
(div b (bex (mul (bex bloq) step)))
::
++ con :: binary or
~/ %con
|= [a=@ b=@]
=+ [c=0 d=0]
|- ^- @
?: ?&(=(0 a) =(0 b)) d
%= $
a (rsh 0 a)
b (rsh 0 b)
c +(c)
d %+ add d
%+ lsh [0 c]
?& =(0 (end 0 a))
=(0 (end 0 b))
==
==
::
++ dis :: binary and
~/ %dis
|= [a=@ b=@]
=| [c=@ d=@]
|- ^- @
?: ?|(=(0 a) =(0 b)) d
%= $
a (rsh 0 a)
b (rsh 0 b)
c +(c)
d %+ add d
%+ lsh [0 c]
?| =(0 (end 0 a))
=(0 (end 0 b))
==
==
::
++ mix :: binary xor
~/ %mix
|= [a=@ b=@]
^- @
=+ [c=0 d=0]
|-
?: ?&(=(0 a) =(0 b)) d
%= $
a (rsh 0 a)
b (rsh 0 b)
c +(c)
d (add d (lsh [0 c] =((end 0 a) (end 0 b))))
==
::
++ lth :: less
~/ %lth
|= [a=@ b=@]
^- ?
?& !=(a b)
|-
?| =(0 a)
?& !=(0 b)
$(a (dec a), b (dec b))
== == ==
::
++ lte :: less or equal
~/ %lte
|= [a=@ b=@]
|(=(a b) (lth a b))
::
++ gte :: greater or equal
~/ %gte
|= [a=@ b=@]
^- ?
!(lth a b)
::
++ gth :: greater
~/ %gth
|= [a=@ b=@]
^- ?
!(lte a b)
::
++ swp :: naive rev bloq order
~/ %swp
|= [a=bloq b=@]
(rep a (flop (rip a b)))
::
++ met :: measure
~/ %met
|= [a=bloq b=@]
^- @
=+ c=0
|-
?: =(0 b) c
$(b (rsh a b), c +(c))
::
++ end :: tail
~/ %end
|= [a=bite b=@]
=/ [=bloq =step] ?^(a a [a *step])
(mod b (bex (mul (bex bloq) step)))
::
++ cat :: concatenate
~/ %cat
|= [a=bloq b=@ c=@]
(add (lsh [a (met a b)] c) b)
::
++ cut :: slice
~/ %cut
|= [a=bloq [b=step c=step] d=@]
(end [a c] (rsh [a b] d))
::
++ can :: assemble
~/ %can
|= [a=bloq b=(list [p=step q=@])]
^- @
?~ b 0
(add (end [a p.i.b] q.i.b) (lsh [a p.i.b] $(b t.b)))
::
++ cad :: assemble specific
~/ %cad
|= [a=bloq b=(list [p=step q=@])]
^- [=step @]
:_ (can a b)
|-
?~ b
0
(add p.i.b $(b t.b))
::
++ rep :: assemble fixed
~/ %rep
|= [a=bite b=(list @)]
=/ [=bloq =step] ?^(a a [a *step])
=| i=@ud
|- ^- @
?~ b 0
%+ add $(i +(i), b t.b)
(lsh [bloq (mul step i)] (end [bloq step] i.b))
::
++ rip :: disassemble
~/ %rip
|= [a=bite b=@]
^- (list @)
?: =(0 b) ~
[(end a b) $(b (rsh a b))]
::
::
:: Lists
::
++ lent :: length
~/ %lent
|= a=(list)
^- @
=+ b=0
|-
?~ a b
$(a t.a, b +(b))
::
++ slag :: suffix
~/ %slag
|* [a=@ b=(list)]
|- ^+ b
?: =(0 a) b
?~ b ~
$(b t.b, a (dec a))
::
++ snag :: index
~/ %snag
|* [a=@ b=(list)]
|- ^+ ?>(?=(^ b) i.b)
?~ b
~_ leaf+"snag-fail"
!!
?: =(0 a) i.b
$(b t.b, a (dec a))
::
++ homo :: homogenize
|* a=(list)
^+ =< $
|@ ++ $ ?:(*? ~ [i=(snag 0 a) t=$])
--
a
::
++ flop :: reverse
~/ %flop
|* a=(list)
=> .(a (homo a))
^+ a
=+ b=`_a`~
|-
?~ a b
$(a t.a, b [i.a b])
::
++ welp :: concatenate
~/ %welp
=| [* *]
|@
++ $
?~ +<-
+<-(. +<+)
+<-(+ $(+<- +<->))
--
::
++ reap :: replicate
~/ %reap
|* [a=@ b=*]
|- ^- (list _b)
?~ a ~
[b $(a (dec a))]
::
:: Modular arithmetic
::
++ fe :: modulo bloq
|_ a=bloq
++ rol |= [b=bloq c=@ d=@] ^- @ :: roll left
=+ e=(sit d)
=+ f=(bex (sub a b))
=+ g=(mod c f)
(sit (con (lsh [b g] e) (rsh [b (sub f g)] e)))
++ sum |=([b=@ c=@] (sit (add b c))) :: wrapping add
++ sit |=(b=@ (end a b)) :: enforce modulo
--
::
:: Hashes
::
++ muk :: standard murmur3
~% %muk ..muk ~
=+ ~(. fe 5)
|= [syd=@ len=@ key=@]
=. syd (end 5 syd)
=/ pad (sub len (met 3 key))
=/ data (welp (rip 3 key) (reap pad 0))
=/ nblocks (div len 4) :: intentionally off-by-one
=/ h1 syd
=+ [c1=0xcc9e.2d51 c2=0x1b87.3593]
=/ blocks (rip 5 key)
=/ i nblocks
=. h1 =/ hi h1 |-
?: =(0 i) hi
=/ k1 (snag (sub nblocks i) blocks) :: negative array index
=. k1 (sit (mul k1 c1))
=. k1 (rol 0 15 k1)
=. k1 (sit (mul k1 c2))
=. hi (mix hi k1)
=. hi (rol 0 13 hi)
=. hi (sum (sit (mul hi 5)) 0xe654.6b64)
$(i (dec i))
=/ tail (slag (mul 4 nblocks) data)
=/ k1 0
=/ tlen (dis len 3)
=. h1
?+ tlen h1 :: fallthrough switch
%3 =. k1 (mix k1 (lsh [0 16] (snag 2 tail)))
=. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
=. k1 (mix k1 (snag 0 tail))
=. k1 (sit (mul k1 c1))
=. k1 (rol 0 15 k1)
=. k1 (sit (mul k1 c2))
(mix h1 k1)
%2 =. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
=. k1 (mix k1 (snag 0 tail))
=. k1 (sit (mul k1 c1))
=. k1 (rol 0 15 k1)
=. k1 (sit (mul k1 c2))
(mix h1 k1)
%1 =. k1 (mix k1 (snag 0 tail))
=. k1 (sit (mul k1 c1))
=. k1 (rol 0 15 k1)
=. k1 (sit (mul k1 c2))
(mix h1 k1)
==
=. h1 (mix h1 len)
|^ (fmix32 h1)
++ fmix32
|= h=@
=. h (mix h (rsh [0 16] h))
=. h (sit (mul h 0x85eb.ca6b))
=. h (mix h (rsh [0 13] h))
=. h (sit (mul h 0xc2b2.ae35))
=. h (mix h (rsh [0 16] h))
h
--
::
++ mug :: mug with murmur3
~/ %mug
|= a=*
|^ ?@ a (mum 0xcafe.babe 0x7fff a)
=/ b (cat 5 $(a -.a) $(a +.a))
(mum 0xdead.beef 0xfffe b)
::
++ mum
|= [syd=@uxF fal=@F key=@]
=/ wyd (met 3 key)
=| i=@ud
|- ^- @F
?: =(8 i) fal
=/ haz=@F (muk syd wyd key)
=/ ham=@F (mix (rsh [0 31] haz) (end [0 31] haz))
?.(=(0 ham) ham $(i +(i), syd +(syd)))
--
::
++ gor :: mug order
~/ %gor
|= [a=* b=*]
^- ?
=+ [c=(mug a) d=(mug b)]
?: =(c d)
(dor a b)
(lth c d)
::
++ mor :: more mug order
~/ %mor
|= [a=* b=*]
^- ?
=+ [c=(mug (mug a)) d=(mug (mug b))]
?: =(c d)
(dor a b)
(lth c d)
::
++ dor :: tree order
~/ %dor
|= [a=* b=*]
^- ?
?: =(a b) &
?. ?=(@ a)
?: ?=(@ b) |
?: =(-.a -.b)
$(a +.a, b +.b)
$(a -.a, b -.b)
?. ?=(@ b) &
(lth a b)
::
++ por :: parent order
~/ %por
|= [a=@p b=@p]
^- ?
?: =(a b) &
=| i=@
|-
?: =(i 2)
:: second two bytes
(lte a b)
:: first two bytes
=+ [c=(end 3 a) d=(end 3 b)]
?: =(c d)
$(a (rsh 3 a), b (rsh 3 b), i +(i))
(lth c d)
::
:: Maps
::
++ by
~/ %by
=| a=(tree (pair)) :: (map)
=* node ?>(?=(^ a) n.a)
|@
++ get
~/ %get
|* b=*
=> .(b `_?>(?=(^ a) p.n.a)`b)
|- ^- (unit _?>(?=(^ a) q.n.a))
?~ a
~
?: =(b p.n.a)
`q.n.a
?: (gor b p.n.a)
$(a l.a)
$(a r.a)
::
++ put
~/ %put
|* [b=* c=*]
|- ^+ a
?~ a
[[b c] ~ ~]
?: =(b p.n.a)
?: =(c q.n.a)
a
a(n [b c])
?: (gor b p.n.a)
=+ d=$(a l.a)
?> ?=(^ d)
?: (mor p.n.a p.n.d)
a(l d)
d(r a(l r.d))
=+ d=$(a r.a)
?> ?=(^ d)
?: (mor p.n.a p.n.d)
a(r d)
d(l a(r l.d))
::
++ del
~/ %del
|* b=*
|- ^+ a
?~ a
~
?. =(b p.n.a)
?: (gor b p.n.a)
a(l $(a l.a))
a(r $(a r.a))
|- ^- [$?(~ _a)]
?~ l.a r.a
?~ r.a l.a
?: (mor p.n.l.a p.n.r.a)
l.a(r $(l.a r.l.a))
r.a(l $(r.a l.r.a))
::
++ apt
=< $
~/ %apt
=| [l=(unit) r=(unit)]
|. ^- ?
?~ a &
?& ?~(l & &((gor p.n.a u.l) !=(p.n.a u.l)))
?~(r & &((gor u.r p.n.a) !=(u.r p.n.a)))
?~ l.a &
&((mor p.n.a p.n.l.a) !=(p.n.a p.n.l.a) $(a l.a, l `p.n.a))
?~ r.a &
&((mor p.n.a p.n.r.a) !=(p.n.a p.n.r.a) $(a r.a, r `p.n.a))
==
--
::
++ on :: ordered map
~/ %on
|* [key=mold val=mold]
=> |%
+$ item [key=key val=val]
--
::
~% %comp +>+ ~
|= compare=$-([key key] ?)
~% %core + ~
|%
::
++ apt
~/ %apt
|= a=(tree item)
=| [l=(unit key) r=(unit key)]
|- ^- ?
?~ a %.y
?& ?~(l %.y (compare key.n.a u.l))
?~(r %.y (compare u.r key.n.a))
?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a)))
?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a)))
==
::
++ get
~/ %get
|= [a=(tree item) b=key]
^- (unit val)
?~ a ~
?: =(b key.n.a)
`val.n.a
?: (compare b key.n.a)
$(a l.a)
$(a r.a)
::
++ has
~/ %has
|= [a=(tree item) b=key]
^- ?
!=(~ (get a b))
::
++ put
~/ %put
|= [a=(tree item) =key =val]
^- (tree item)
?~ a [n=[key val] l=~ r=~]
?: =(key.n.a key) a(val.n val)
?: (compare key key.n.a)
=/ l $(a l.a)
?> ?=(^ l)
?: (mor key.n.a key.n.l)
a(l l)
l(r a(l r.l))
=/ r $(a r.a)
?> ?=(^ r)
?: (mor key.n.a key.n.r)
a(r r)
r(l a(r l.r))
--
::
:: Sets
::
++ in
~/ %in
=| a=(tree) :: (set)
|@
++ put
~/ %put
|* b=*
|- ^+ a
?~ a
[b ~ ~]
?: =(b n.a)
a
?: (gor b n.a)
=+ c=$(a l.a)
?> ?=(^ c)
?: (mor n.a n.c)
a(l c)
c(r a(l r.c))
=+ c=$(a r.a)
?> ?=(^ c)
?: (mor n.a n.c)
a(r c)
c(l a(r l.c))
::
++ del
~/ %del
|* b=*
|- ^+ a
?~ a
~
?. =(b n.a)
?: (gor b n.a)
a(l $(a l.a))
a(r $(a r.a))
|- ^- [$?(~ _a)]
?~ l.a r.a
?~ r.a l.a
?: (mor n.l.a n.r.a)
l.a(r $(l.a r.l.a))
r.a(l $(r.a l.r.a))
::
++ apt
=< $
~/ %apt
=| [l=(unit) r=(unit)]
|. ^- ?
?~ a &
?& ?~(l & (gor n.a u.l))
?~(r & (gor u.r n.a))
?~(l.a & ?&((mor n.a n.l.a) $(a l.a, l `n.a)))
?~(r.a & ?&((mor n.a n.r.a) $(a r.a, r `n.a)))
==
--
::
:: Jugs
::
++ ju
=| a=(tree (pair * (tree))) :: (jug)
|@
++ get
|* b=*
=+ c=(~(get by a) b)
?~(c ~ u.c)
::
++ del
|* [b=* c=*]
^+ a
=+ d=(get b)
=+ e=(~(del in d) c)
?~ e
(~(del by a) b)
(~(put by a) b e)
::
++ put
|* [b=* c=*]
^+ a
=+ d=(get b)
(~(put by a) b (~(put in d) c))
--
--

161
desk/lib/ucm.hoon Normal file
View File

@ -0,0 +1,161 @@
|%
::
:: XX should add a +default-text with the intro / tutorial blog post
++ default-theme
^- @t
'''
body {
margin: 5vh 5vw 5vh 5vw;
max-width: 650px;
font-size: 19px;
text-align: left-align;
background-color: #fefefe;
}
h1, h2, h3, h4, h5, h6, p {
color: #010101;
}
code {
color: #010101;
background-color: #e5e5e5;
font-size: 16px;
}
img {
margin: auto;
max-height: 100%;
max-width: 100%;
display: block;
}
'''
::
++ add-style
|= [html=@t css=@t]
(cat 3 html (set-style css))
++ set-style
|= css=@t
^- @t
(cat 3 (cat 3 '<style>' css) '</style>')
::
++ http-response-cards
|= [id=@tas hed=response-header:http data=(unit octs)]
^- (list card:agent:gall)
=/ paths [/http-response/[id]]~
:~ [%give %fact paths %http-response-header !>(hed)]
[%give %fact paths %http-response-data !>(data)]
[%give %kick paths ~]
==
++ serve
|= [eyre-id=@ta =simple-payload:http]
^- (list card:agent:gall)
=/ header-cage
[%http-response-header !>(response-header.simple-payload)]
=/ data-cage
[%http-response-data !>(data.simple-payload)]
:~ [%give %fact ~[/http-response/[eyre-id]] header-cage]
[%give %fact ~[/http-response/[eyre-id]] data-cage]
[%give %kick ~[/http-response/[eyre-id]] ~]
==
+$ request-line
$: [ext=(unit @ta) site=(list @t)]
args=(list [key=@t value=@t])
==
:: +parse-request-line: take a cord and parse out a url
::
++ parse-request-line
|= url=@t
^- request-line
(fall (rush url ;~(plug apat:de-purl:html yque:de-purl:html)) [[~ ~] ~])
++ serve-manx |= =manx ^- simple-payload:http
:- [200 [['content-type' 'text/html'] ~]]
`(as-octt:mimes:html (en-xml:html manx))
++ serve-file |= [name=@t htmls=@t] ^- simple-payload:http
:- [200 [['content-type' 'text/html'] ~]]
`(as-octs:mimes:html htmls)
++ insert |= [layout=manx body=marl] ^- manx
=/ m layout
|-
?~ c.m m
?: .=(%body n.g.i.c.m)
=. c.i.c.m (weld c.i.c.m body) m
$(c.m t.c.m)
++ login-page
|= redirect=tape
^- manx
:: TODO
=/ css ^~ %- trip
'''
#login{
margin-top: 3rem;
& h1, & p {
text-align: center;
}
& form{
margin: auto;
width: 50%;
text-align: center;
& input[type=text]{
outline: none;
padding: 0.5rem;
}
& button{
display: block;
margin: 1rem auto;
padding: 0.4rem;
background-color: white;
}
}
}
'''
;html
;head
;title:"Sblog"
;meta(charset "utf-8");
==
;body
;div#login.blog
;style: {css}
;h1: Login
;p: Please login to your Urbit ID to access this page.
;form(action "/~/login", method "post")
;input.mono(type "text")
=name "name"
=id "name"
=placeholder "~sorreg-namtyv"
=required "true"
=minlength "4"
=maxlength "14"
=pattern "~((([a-z]\{6})\{1,2}-\{0,2})+|[a-z]\{3})";
;input(type "hidden", name "redirect", value redirect);
;button(name "eauth", type "submit"):"Login"
==
==
==
==
++ not-found-page ^- manx
:: TODO
;html
;head
;title:"Sblog"
;meta(charset "utf-8");
==
;body
;p:"404"
==
==
++ layout ^- manx
;html
;head
;title:"Sblog"
;meta(charset "utf-8");
==
;body
;span;
==
==
--

319
desk/lib/web.hoon Normal file
View File

@ -0,0 +1,319 @@
/- sur=ucm, docket
/+ server, jon=json, sr=sortug, metamask, cons=constants
|_ [=bowl:gall eyre-id=@ta req=inbound-request:eyre =state:sur]
+$ card card:agent:gall
++ session-timeout ~d300
::
++ get-file-at
|= [base=path file=path ext=@ta]
^- (unit octs)
=/ =path
:* (scot %p our.bowl)
q.byk.bowl
(scot %da now.bowl)
(snoc (weld base file) ext)
==
?. .^(? %cu path) ~
%- some
%- as-octs:mimes:html
.^(@ %cx path)
++ handle-get-request
|= [headers=header-list:http request-line:server]
^- simple-payload:http
?~ ext $(ext `%html, site [%index ~])
?: ?=([%ucm *] site) $(site +.site)
:: serve dynamic session.js
::
?: =([/session `%js] [site ext])
%- js-response:gen:server
%- as-octt:mimes:html
"""
window.ship = '{(scow %p src.bowl)}';
"""
=/ file=(unit octs)
(get-file-at /web site u.ext)
?~ file ~& "file not found" not-found:gen:server
?+ u.ext not-found:gen:server
%html (html-response:gen:server u.file)
%js (js-response:gen:server u.file)
%css (css-response:gen:server u.file)
%png (png-response:gen:server u.file)
==
++ route
:: set cookie
=/ metalib ~(. metamask [sessions.state bowl])
=/ rl (parse-request-line:server url.request.req)
=/ sitepath=path /[(head site.rl)]
=/ pat=(pole knot) site.rl
?: .=(%'PUT' method.request.req) (ninja-put pat)
?+ pat ebail
[%ucm %metamask rest=*] (serve-metamask-challenge:metalib eyre-id)
[%ucm %auth rest=*] (process-metamask-auth:metalib eyre-id body.request.req)
[%ucm %api rest=*] (serve-site-json rest.pat)
[%ucm %napi rest=*] (ninja-scry rest.pat)
[site=@t *] (send-response (handle-get-request header-list.request.req rl))
==
+$ channel-request
$% :: %ack: acknowledges that the client has received events up to :id
::
[%ack event-id=@ud]
:: %poke: pokes an application, validating :noun against :mark
::
[%poke request-id=@ud ship=@p app=term mark=@tas =noun]
:: %poke-json: pokes an application, translating :json to :mark
::
:: TODO important!! must add desk
[%poke-json request-id=@ud ship=@p desk=term app=term mark=@tas =json]
:: %watch: subscribes to an application path
::
[%subscribe request-id=@ud ship=@p app=term =path]
:: %leave: unsubscribes from an application path
::
[%unsubscribe request-id=@ud subscription-id=@ud]
:: %delete: kills a channel
::
[%delete ~]
==
++ parse-channel-request-json
|= request-list=json
^- (unit (list channel-request))
:: parse top
::
=, dejs-soft:format
=- ((ar -) request-list)
::
|= item=json
^- (unit channel-request)
::
?~ maybe-key=((ot action+so ~) item)
~
?: =('ack' u.maybe-key)
((pe %ack (ot event-id+ni ~)) item)
?: =('poke' u.maybe-key)
%. item
%+ pe %poke-json
:: TODO important!! must add desk
(ot id+ni ship+(su fed:ag) desk+so app+so mark+(su sym) json+some ~)
?: =('subscribe' u.maybe-key)
%. item
%+ pe %subscribe
(ot id+ni ship+(su fed:ag) app+so path+(su stap) ~)
?: =('unsubscribe' u.maybe-key)
%. item
%+ pe %unsubscribe
(ot id+ni subscription+ni ~)
?: =('delete' u.maybe-key)
`[%delete ~]
:: if we reached this, we have an invalid action key. fail parsing.
::
~
++ ninja-put |= pat=(pole knot)
^- (list card)
=/ octs body.request.req
?~ octs ~|(%empty-auth-request !!)
=/ jon (de:json:html q.u.octs)
?~ jon ~|(%empty-auth-json !!)
%+ weld
%- send-response
(json-response:gen:server [%s 'ok'])
=/ body=json u.jon
=/ mchan-reqs (parse-channel-request-json body)
?~ mchan-reqs ~
%+ roll u.mchan-reqs |= [cr=channel-request acc=(list card)]
?+ -.cr acc
%poke-json
:: TODO channels has ?>(.=(our.bowl src.bowk))
:: channels-server has ?>(.=(src.bowl author.essay))
:: damn wat do
=/ =mars:clay [%json mark.cr]
=/ mars-path /[a.mars]/[b.mars]
=/ base /(scot %p our.bowl)/[desk.cr]/(scot %da now.bowl)
=/ scry-path (weld base mars-path)
=/ mark-conversion-gate .^(tube:clay %cc scry-path)
=/ vas (mark-conversion-gate !>(json.cr))
:_ acc
[%pass /ninja-poke %agent [ship.cr app.cr] %poke mark.cr vas]
==
:: ~& ninja-put=pat
:: ?. ?=([%ucm rest=*] pat) !!
:: =/ channel-url (path:enjs:format rest.pat)
:: ?. ?=(%s -.channel-url) !!
:: :_ ~
:: [%pass /ninja-poke %arvo %k %fard %ucm %iris-thread %noun !>([+.channel-url request.req])]
:: ~& ninja-put=req
:: =/ octs body.request.req
:: ?~ octs ~|(%empty-auth-request !!)
:: =/ jon (de:json:html q.u.octs)
:: ?~ jon ~|(%empty-auth-json !!)
:: =/ body=json u.jon
:: ~& (en:json:html body)
++ session-cookie-string
|= [session=@uv extend=?]
^- @t
%- crip
=; max-age=tape
:: "urbauth-{(scow %p src.bowl)}={(scow %uv session)}; Path=/; Max-Age={max-age}"
"ucm-{(scow %p src.bowl)}={(scow %uv session)}; Path=/; Max-Age={max-age}"
%+ scow:parsing:sr %ud
?. extend 0
(div (msec:milly session-timeout) 1.000)
++ session-id-from-request
|= =request:http
^- (unit @uv)
:: are there cookies passed with this request?
::
=/ cookie-header=@t
%+ roll header-list.request
|= [[key=@t value=@t] c=@t]
?. =(key 'cookie')
c
(cat 3 (cat 3 c ?~(c 0 '; ')) value)
:: is the cookie line valid?
::
?~ cookies=(rush cookie-header cock:de-purl:html)
~
:: is there an urbauth cookie?
::
?~ urbauth=(get-header:http (crip "urblog-{(scow %p src.bowl)}") u.cookies)
~
:: if it's formatted like a valid session cookie, produce it
::
`(unit @)`(rush u.urbauth ;~(pfix (jest '0v') viz:ag))
++ send-response
|= =simple-payload:http
=/ cookie ['set-cookie' (session-cookie-string 0vublog .y)]
=. headers.response-header.simple-payload
[cookie headers.response-header.simple-payload]
%+ give-simple-payload:app:server eyre-id simple-payload
++ ebail
^- (list card:agent:gall)
(send-response pbail)
++ egive
|= pl=simple-payload:http
^- (list card:agent:gall)
(send-response pl)
++ pbail
%- html-response:gen:server
%- manx-to-octs:server
manx-bail
++ manx-bail ^- manx ;div:"404"
++ manx-payload
|= =manx
^- simple-payload:http
%- html-response:gen:server
%- manx-to-octs:server manx
::
++ serve-site-json :: serve site state and group state
|= =path
=/ site (~(get by sites.state) path)
?~ site ebail
=/ site-j=json (en-site:en:jon u.site)
=/ group-j .^(json %gx /(scot %p our.bowl)/groups/(scot %da now.bowl)/groups/(scot %p our.bowl)/[groupname.u.site]/v1/json)
=/ j=json %- pairs:enjs:format
:~ group+group-j
site+site-j
==
%- send-response
(json-response:gen:server j)
++ ninja-scry :: serve site state and group state
|= pat=path
:: ~& >> ninja-scry=[eyre-id pat]
?~ pat !!
=/ [app=@t rest=path] pat
=/ base /(scot %p our.bowl)/[app]/(scot %da now.bowl)
=/ scrypath (weld (weld base rest) /json)
=/ res .^(json %gx scrypath)
%- send-response
(json-response:gen:server res)
:: async function scryGroups() {
:: const app = "groups";
:: const path = "/groups/v1";
:: return await scry(app, path);
:: }
:: async function scryDiary(
:: ship: Ship,
:: name: string,
:: count: number,
:: ): Promise<DiaryPage> {
:: const app = "channels";
:: const path = `/v1/diary/${ship}/${name}/posts/newest/${count}/post`;
:: return await scry(app, path);
:: }
:: async function scryDiaryPost(
:: ship: Ship,
:: name: string,
:: id: string,
:: ): Promise<DiaryPost> {
:: const app = "channels";
:: const path = `/v1/diary/${ship}/${name}/posts/post/${id}`;
:: return await scry(app, path);
:: }
:: async function scryChat(
:: ship: Ship,
:: name: string,
:: count: number,
:: ): Promise<PostsPage> {
:: const app = "channels";
:: const path = `/v1/chat/${ship}/${name}/posts/newest/${count}/post`;
:: return await scry(app, path);
:: }
:: // other apps
:: async function scryPikes() {
:: const app = "hood";
:: const path = "/kiln/pikes";
:: return await scry(app, path);
:: }
++ payload-from-glob
|= [=glob:docket what=request-line:server eyre-id=@ta]
%- send-response
^- simple-payload:http
=/ suffix=path
(weld site.what (drop ext.what))
?: =(suffix /desk/js)
%- inline-js-response
(rap 3 'window.desk = "' %ucm '";' ~)
=? suffix !(~(has by glob) suffix)
(turn suffix |=(s=@t (crip (en-urlt:html (trip s)))))
=/ requested
?: (~(has by glob) suffix) suffix
%+ weld suffix /index/html
=/ data=mime
(~(got by glob) requested)
=/ mime-type=@t (rsh 3 (crip <p.data>))
=; headers
[[200 headers] `q.data]
:- content-type+mime-type
?: =(/index/html requested) ~
~[max-1-wk:gen:server]
++ inline-js-response
|= js=cord
^- simple-payload:http
%. (as-octs:mimes:html js)
%* . js-response:gen:server
cache %.n
==
--

16
desk/mar/arch.hoon Normal file
View File

@ -0,0 +1,16 @@
|_ arch=(list @ta)
::
++ grab
|%
++ noun (list @ta)
--
::
++ grow
|%
++ noun arch
++ json
[%a (turn arch |=(c=@ta [%s c]))]
--
::
++ grad %noun
--

34
desk/mar/bill.hoon Normal file
View File

@ -0,0 +1,34 @@
|_ bil=(list dude:gall)
++ grow
|%
++ mime `^mime`[/text/x-bill (as-octs:mimes:html hoon)]
++ noun bil
++ hoon
^- @t
|^ (crip (of-wall:format (wrap-lines (spit-duz bil))))
::
++ wrap-lines
|= taz=wall
^- wall
?~ taz ["~"]~
:- (weld ":~ " i.taz)
%- snoc :_ "=="
(turn t.taz |=(t=tape (weld " " t)))
::
++ spit-duz
|= duz=(list dude:gall)
^- wall
(turn duz |=(=dude:gall ['%' (trip dude)]))
--
++ txt (to-wain:format hoon)
--
++ grab
|%
++ noun (list dude:gall)
++ mime
|= [=mite len=@ud tex=@]
~_ tex
!<((list dude:gall) (slap !>(~) (ream tex)))
--
++ grad %noun
--

21
desk/mar/css.hoon Normal file
View File

@ -0,0 +1,21 @@
::
:::: /hoon/css/mar
::
/? 310
=, eyre
=, mimes:html
|_ mud=@t
++ grow :: convert to
|% ++ mime [/text/css (as-octs mud)] :: convert to %mime
++ elem ;style :: convert to %hymn
;- (trip mud)
==
++ hymn ;html:(head:"{elem}" body)
--
++ grab
|% :: convert from
++ mime |=([p=mite q=octs] (@t q.q))
++ noun @t :: clam from %noun
--
++ grad %mime
--

25
desk/mar/docket-0.hoon Normal file
View File

@ -0,0 +1,25 @@
/+ dock=docket
|_ =docket:dock
++ grow
|%
++ mime
^- ^mime
[/text/x-docket (as-octt:mimes:html (spit-docket:mime:dock docket))]
++ noun docket
++ json (docket:enjs:dock docket)
--
++ grab
|%
::
++ mime
|= [=mite len=@ud tex=@]
^- docket:dock
%- need
%- from-clauses:mime:dock
!<((list clause:dock) (slap !>(~) (ream tex)))
::
++ noun docket:dock
--
++ grad %noun
--

View File

@ -0,0 +1,14 @@
|_ [=ship =desk]
++ grad %noun
++ grow
|%
++ noun [ship desk]
++ json `^json`s+(crip "{(scow %p ship)}/{(trip desk)}")
--
++ grab
|%
++ noun _[ship desk]
++ json
(su:dejs:format ;~((glue fas) ;~(pfix sig fed:ag) sym))
--
--

View File

@ -0,0 +1,13 @@
|_ =desk
++ grad %noun
++ grow
|%
++ noun desk
++ json s+desk
--
++ grab
|%
++ noun ^desk
++ json so:dejs:format
--
--

49
desk/mar/hoon.hoon Normal file
View File

@ -0,0 +1,49 @@
:::: /hoon/hoon/mar
::
/? 310
::
=, eyre
|_ own=@t
::
++ grow :: convert to
|%
++ mime `^mime`[/text/x-hoon (as-octs:mimes:html own)] :: convert to %mime
++ elem :: convert to %html
;div:pre(urb_codemirror "", mode "hoon"):"{(trip own)}"
:: =+ gen-id="src-{<`@ui`(mug own)>}"
:: ;div
:: ;textarea(id "{gen-id}"):"{(trip own)}"
:: ;script:"""
:: CodeMirror.fromTextArea(
:: window[{<gen-id>}],
:: \{lineNumbers:true, readOnly:true}
:: )
:: """
:: ==
++ hymn
:: ;html:(head:title:"Source" "+{elem}")
;html
;head
;title:"Source"
;script@"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.3.0/codemirror.js";
;script@"/lib/syntax/hoon.js";
;link(rel "stylesheet", href "//cdnjs.cloudflare.com/ajax/libs/".
"codemirror/4.3.0/codemirror.min.css");
;link/"/lib/syntax/codemirror.css"(rel "stylesheet");
==
;body
;textarea#src:"{(trip own)}"
;script:'CodeMirror.fromTextArea(src, {lineNumbers:true, readOnly:true})'
==
==
++ txt
(to-wain:format own)
--
++ grab
|% :: convert from
++ mime |=([p=mite q=octs] q.q)
++ noun @t :: clam from %noun
++ txt of-wain:format
--
++ grad %txt
--

22
desk/mar/html.hoon Normal file
View File

@ -0,0 +1,22 @@
::
:::: /hoon/html/mar
::
/? 310
::
:::: compute
::
=, html
|_ htm=@t
++ grow :: convert to
^?
|% ::
++ mime [/text/html (met 3 htm) htm] :: to %mime
++ hymn (need (de-xml htm)) :: to %hymn
-- ::
++ grab ^?
|% :: convert from
++ noun @t :: clam from %noun
++ mime |=([p=mite q=octs] q.q) :: retrieve form %mime
--
++ grad %mime
--

12
desk/mar/ico.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/image/x-icon (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

25
desk/mar/js.hoon Normal file
View File

@ -0,0 +1,25 @@
::
:::: /hoon/js/mar
::
/? 310
::
=, eyre
|_ mud=@
++ grow
|%
++ mime [/application/javascript (as-octs:mimes:html (@t mud))]
++ hymn :: convert to %hymn
|^ html
++ script ;script
;- (trip (@t mud))
==
++ html ;html:(head:"{script}" body)
--
--
++ grab
|% :: convert from
++ mime |=([p=mite q=octs] (@t q.q))
++ noun cord :: clam from %noun
--
++ grad %mime
--

26
desk/mar/json.hoon Normal file
View File

@ -0,0 +1,26 @@
::
:::: /hoon/json/mar
::
/? 310
::
:::: compute
::
=, eyre
=, format
=, html
|_ jon=^json
::
++ grow :: convert to
|%
++ mime [/application/json (as-octs:mimes -:txt)] :: convert to %mime
++ txt [(en:json jon)]~
--
++ grab
|% :: convert from
++ mime |=([p=mite q=octs] (fall (de:json (@t q.q)) *^json))
++ noun ^json :: clam from %noun
++ numb numb:enjs
++ time time:enjs
--
++ grad %mime
--

28
desk/mar/kelvin.hoon Normal file
View File

@ -0,0 +1,28 @@
|_ kal=waft:clay
++ grow
|%
++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
++ noun kal
++ hoon
%+ rap 3
%+ turn
%+ sort
~(tap in (waft-to-wefts:clay kal))
|= [a=weft b=weft]
?: =(lal.a lal.b)
(gte num.a num.b)
(gte lal.a lal.b)
|= =weft
(rap 3 '[%' (scot %tas lal.weft) ' ' (scot %ud num.weft) ']\0a' ~)
::
++ txt (to-wain:format hoon)
--
++ grab
|%
++ noun waft:clay
++ mime
|= [=mite len=@ud tex=@]
(cord-to-waft:clay tex)
--
++ grad %noun
--

20
desk/mar/md.hoon Normal file
View File

@ -0,0 +1,20 @@
::
:::: /hoon/md/mar
::
/? 310
::
=, format
=, mimes:html
|_ txt=wain
::
++ grab :: convert from
|%
++ mime |=((pair mite octs) (to-wain q.q))
++ noun wain :: clam from %noun
--
++ grow
|%
++ mime [/text/plain (as-octs (of-wain txt))]
--
++ grad %mime
--

32
desk/mar/mime.hoon Normal file
View File

@ -0,0 +1,32 @@
::
:::: /hoon/mime/mar
::
/? 310
::
|_ own=mime
++ grow
^?
|%
++ jam `@`q.q.own
--
::
++ grab :: convert from
^?
|%
++ noun mime :: clam from %noun
++ tape
|=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)])
--
++ grad
^?
|%
++ form %mime
++ diff |=(mime +<)
++ pact |=(mime +<)
++ join |=([mime mime] `(unit mime)`~)
++ mash
|= [[ship desk mime] [ship desk mime]]
^- mime
~|(%mime-mash !!)
--
--

26
desk/mar/ninja.hoon Normal file
View File

@ -0,0 +1,26 @@
::
:::: /hoon/json/mar
::
/? 310
::
:::: compute
::
=, eyre
=, format
=, html
|_ jon=^json
::
++ grow :: convert to
|%
++ mime [/application/json (as-octs:mimes -:txt)] :: convert to %mime
++ txt [(en:json jon)]~
--
++ grab
|% :: convert from
++ mime |=([p=mite q=octs] (fall (de:json (@t q.q)) *^json))
++ noun ^json :: clam from %noun
++ numb numb:enjs
++ time time:enjs
--
++ grad %mime
--

22
desk/mar/noun.hoon Normal file
View File

@ -0,0 +1,22 @@
::
:::: /hoon/noun/mar
::
/? 310
!:
|_ non=*
++ grab |%
++ noun *
++ mime |=([p=mite q=octs] (cue q.q))
--
++ grow |%
++ mime [/application/octet-stream (as-octs:mimes:html (jam non))]
--
++ grad
|%
++ form %noun
++ diff |=(* +<)
++ pact |=(* +<)
++ join |=([* *] *(unit *))
++ mash |=([[ship desk *] [ship desk *]] `*`~|(%noun-mash !!))
--
--

12
desk/mar/png.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/image/png (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

20
desk/mar/ship.hoon Normal file
View File

@ -0,0 +1,20 @@
|_ s=ship
++ grad %noun
++ grow
|%
++ noun s
++ json s+(scot %p s)
++ mime
^- ^mime
[/text/x-ship (as-octt:mimes:html (scow %p s))]
--
++ grab
|%
++ noun ship
++ json (su:dejs:format ;~(pfix sig fed:ag))
++ mime
|= [=mite len=@ tex=@]
(slav %p (snag 0 (to-wain:format tex)))
--
--

View File

@ -0,0 +1,6 @@
::
:: XX remove this file in transition to %4
::
/- blog-paths
/+ *sss
(mk-mar blog-paths)

15
desk/mar/sss/to-pub.hoon Normal file
View File

@ -0,0 +1,15 @@
::
:: XX remove this file in transition to %4
::
/- *sss
|_ =(request:poke)
++ grow
|%
++ noun request
--
++ grab
|%
++ noun (request:poke)
--
++ grad %noun
--

12
desk/mar/svg.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/image/'svg+xml' (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

274
desk/mar/txt.hoon Normal file
View File

@ -0,0 +1,274 @@
::
:::: /hoon/txt/mar
::
/? 310
::
=, clay
=, differ
=, format
=, mimes:html
|_ txt=wain
::
++ grab :: convert from
|%
++ mime |=((pair mite octs) (to-wain q.q))
++ noun wain :: clam from %noun
--
++ grow
=> v=.
|%
++ mime => v [/text/plain (as-octs (of-wain txt))]
--
++ grad
|%
++ form %txt-diff
++ diff
|= tyt=wain
^- (urge cord)
(lusk txt tyt (loss txt tyt))
::
++ pact
|= dif=(urge cord)
~| [%pacting dif]
^- wain
(lurk txt dif)
::
++ join
|= [ali=(urge cord) bob=(urge cord)]
^- (unit (urge cord))
|^
=. ali (clean ali)
=. bob (clean bob)
|- ^- (unit (urge cord))
?~ ali `bob
?~ bob `ali
?- -.i.ali
%&
?- -.i.bob
%&
?: =(p.i.ali p.i.bob)
%+ bind $(ali t.ali, bob t.bob)
|=(cud=(urge cord) [i.ali cud])
?: (gth p.i.ali p.i.bob)
%+ bind $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)
|=(cud=(urge cord) [i.bob cud])
%+ bind $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))
|=(cud=(urge cord) [i.ali cud])
::
%|
?: =(p.i.ali (lent p.i.bob))
%+ bind $(ali t.ali, bob t.bob)
|=(cud=(urge cord) [i.bob cud])
?: (gth p.i.ali (lent p.i.bob))
%+ bind $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)
|=(cud=(urge cord) [i.bob cud])
~
==
::
%|
?- -.i.bob
%|
?. =(i.ali i.bob)
~
%+ bind $(ali t.ali, bob t.bob)
|=(cud=(urge cord) [i.ali cud])
::
%&
?: =(p.i.bob (lent p.i.ali))
%+ bind $(ali t.ali, bob t.bob)
|=(cud=(urge cord) [i.ali cud])
?: (gth p.i.bob (lent p.i.ali))
%+ bind $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))
|=(cud=(urge cord) [i.ali cud])
~
==
==
++ clean :: clean
|= wig=(urge cord)
^- (urge cord)
?~ wig ~
?~ t.wig wig
?: ?=(%& -.i.wig)
?: ?=(%& -.i.t.wig)
$(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig])
[i.wig $(wig t.wig)]
?: ?=(%| -.i.t.wig)
$(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig])
[i.wig $(wig t.wig)]
--
::
++ mash
|= $: [als=ship ald=desk ali=(urge cord)]
[bos=ship bod=desk bob=(urge cord)]
==
^- (urge cord)
|^
=. ali (clean ali)
=. bob (clean bob)
|- ^- (urge cord)
?~ ali bob
?~ bob ali
?- -.i.ali
%&
?- -.i.bob
%&
?: =(p.i.ali p.i.bob)
[i.ali $(ali t.ali, bob t.bob)]
?: (gth p.i.ali p.i.bob)
[i.bob $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)]
[i.ali $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))]
::
%|
?: =(p.i.ali (lent p.i.bob))
[i.bob $(ali t.ali, bob t.bob)]
?: (gth p.i.ali (lent p.i.bob))
[i.bob $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)]
=/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
(resolve ali bob)
[fic $(ali ali, bob bob)]
:: ~ :: here, alice is good for a while, but not for the whole
== :: length of bob's changes
::
%|
?- -.i.bob
%|
=/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
(resolve ali bob)
[fic $(ali ali, bob bob)]
::
%&
?: =(p.i.bob (lent p.i.ali))
[i.ali $(ali t.ali, bob t.bob)]
?: (gth p.i.bob (lent p.i.ali))
[i.ali $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))]
=/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
(resolve ali bob)
[fic $(ali ali, bob bob)]
==
==
::
++ annotate :: annotate conflict
|= $: ali=(list @t)
bob=(list @t)
bas=(list @t)
==
^- (list @t)
%- zing
^- (list (list @t))
%- flop
^- (list (list @t))
:- :_ ~
%^ cat 3 '<<<<<<<<<<<<'
%^ cat 3 ' '
%^ cat 3 `@t`(scot %p bos)
%^ cat 3 '/'
bod
:- bob
:- ~['------------']
:- bas
:- ~['++++++++++++']
:- ali
:- :_ ~
%^ cat 3 '>>>>>>>>>>>>'
%^ cat 3 ' '
%^ cat 3 `@t`(scot %p als)
%^ cat 3 '/'
ald
~
::
++ clean :: clean
|= wig=(urge cord)
^- (urge cord)
?~ wig ~
?~ t.wig wig
?: ?=(%& -.i.wig)
?: ?=(%& -.i.t.wig)
$(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig])
[i.wig $(wig t.wig)]
?: ?=(%| -.i.t.wig)
$(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig])
[i.wig $(wig t.wig)]
::
++ resolve
|= [ali=(urge cord) bob=(urge cord)]
^- [fic=[%| p=(list cord) q=(list cord)] ali=(urge cord) bob=(urge cord)]
=- [[%| bac (annotate alc boc bac)] ali bob]
|- ^- $: $: bac=(list cord)
alc=(list cord)
boc=(list cord)
==
ali=(urge cord)
bob=(urge cord)
==
?~ ali [[~ ~ ~] ali bob]
?~ bob [[~ ~ ~] ali bob]
?- -.i.ali
%&
?- -.i.bob
%& [[~ ~ ~] ali bob] :: no conflict
%|
=+ lob=(lent p.i.bob)
?: =(lob p.i.ali)
[[p.i.bob p.i.bob q.i.bob] t.ali t.bob]
?: (lth lob p.i.ali)
[[p.i.bob p.i.bob q.i.bob] [[%& (sub p.i.ali lob)] t.ali] t.bob]
=+ wat=(scag (sub lob p.i.ali) p.i.bob)
=+ ^= res
%= $
ali t.ali
bob [[%| (scag (sub lob p.i.ali) p.i.bob) ~] t.bob]
==
:* :* (welp bac.res wat)
(welp alc.res wat)
(welp boc.res q.i.bob)
==
ali.res
bob.res
==
==
::
%|
?- -.i.bob
%&
=+ loa=(lent p.i.ali)
?: =(loa p.i.bob)
[[p.i.ali q.i.ali p.i.ali] t.ali t.bob]
?: (lth loa p.i.bob)
[[p.i.ali q.i.ali p.i.ali] t.ali [[%& (sub p.i.bob loa)] t.bob]]
=+ wat=(slag (sub loa p.i.bob) p.i.ali)
=+ ^= res
%= $
ali [[%| (scag (sub loa p.i.bob) p.i.ali) ~] t.ali]
bob t.bob
==
:* :* (welp bac.res wat)
(welp alc.res q.i.ali)
(welp boc.res wat)
==
ali.res
bob.res
==
::
%|
=+ loa=(lent p.i.ali)
=+ lob=(lent p.i.bob)
?: =(loa lob)
[[p.i.ali q.i.ali q.i.bob] t.ali t.bob]
=+ ^= res
?: (gth loa lob)
$(ali [[%| (scag (sub loa lob) p.i.ali) ~] t.ali], bob t.bob)
~& [%scagging loa=loa pibob=p.i.bob slag=(scag loa p.i.bob)]
$(ali t.ali, bob [[%| (scag (sub lob loa) p.i.bob) ~] t.bob])
:* :* (welp bac.res ?:((gth loa lob) p.i.bob p.i.ali))
(welp alc.res q.i.ali)
(welp boc.res q.i.bob)
==
ali.res
bob.res
==
==
==
--
--
--

12
desk/mar/webmanifest.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/image/png (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

12
desk/mar/woff.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/font/woff (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

12
desk/mar/woff2.hoon Normal file
View File

@ -0,0 +1,12 @@
|_ dat=@
++ grow
|%
++ mime [/font/woff2 (as-octs:mimes:html dat)]
--
++ grab
|%
++ mime |=([p=mite q=octs] q.q)
++ noun @
--
++ grad %mime
--

21
desk/sur/blog-paths.hoon Normal file
View File

@ -0,0 +1,21 @@
::
:: XX remove this file in transition to %4
::
|%
++ name %blog-paths
+$ rock [uri=@t paths=(set path)]
+$ wave
$% [%init paths=(set path)]
[%post =path]
[%depost =path]
[%uri uri=@t]
==
++ wash
|= [=rock =wave]
?- -.wave
%init rock(paths paths.wave)
%post rock(paths (~(put in paths.rock) path.wave))
%depost rock(paths (~(del in paths.rock) path.wave))
%uri rock(uri uri.wave)
==
--

82
desk/sur/docket.hoon Normal file
View File

@ -0,0 +1,82 @@
|%
::
+$ version
[major=@ud minor=@ud patch=@ud]
::
+$ glob (map path mime)
::
+$ url cord
:: $glob-location: How to retrieve a glob
::
+$ glob-reference
[hash=@uvH location=glob-location]
::
+$ glob-location
$% [%http =url]
[%ames =ship]
==
:: $href: Where a tile links to
::
+$ href
$% [%glob base=term =glob-reference]
[%site =path]
==
:: $chad: State of a docket
::
+$ chad
$~ [%install ~]
$% :: Done
[%glob =glob]
[%site ~]
:: Waiting
[%install ~]
[%suspend glob=(unit glob)]
:: Error
[%hung err=cord]
==
::
:: $charge: A realized $docket
::
+$ charge
$: =docket
=chad
==
::
:: $clause: A key and value, as part of a docket
::
:: Only used to parse $docket
::
+$ clause
$% [%title title=@t]
[%info info=@t]
[%color color=@ux]
[%glob-http url=cord hash=@uvH]
[%glob-ames =ship hash=@uvH]
[%image =url]
[%site =path]
[%base base=term]
[%version =version]
[%website website=url]
[%license license=cord]
==
::
:: $docket: A description of JS bundles for a desk
::
+$ docket
$: %1
title=@t
info=@t
color=@ux
=href
image=(unit url)
=version
website=url
license=cord
==
::
+$ charge-update
$% [%initial initial=(map desk charge)]
[%add-charge =desk =charge]
[%del-charge =desk]
==
--

392
desk/sur/hood.hoon Normal file
View File

@ -0,0 +1,392 @@
/% kelvin %kelvin
=, clay
=* dude dude:gall
|%
+$ pike
$: sync=(unit [=ship =desk])
hash=@uv
=zest
wic=(set weft)
==
::
+$ pikes (map desk pike)
:: $jump: changes to update source change requests
::
+$ jump
$% [%all all=(map dock dock)] :: pending requests
[%add old=dock new=dock] :: new request
[%yea old=dock new=dock] :: approved
[%nay old=dock new=dock] :: denied
==
:: $rung: reference to upstream commit
::
+$ rung [=aeon =weft]
:: #sync-record: source and destination of a kiln sync
::
+$ sync-record ::
$: syd=desk :: local desk
her=ship :: foreign ship
sud=desk :: foreign desk
==
::
+$ sync-state ::
$: nun=@ta :: nonce
kid=(unit desk) :: has kids desk too?
let=@ud :: next revision
nit=(unit ?) :: automerge or default
hav=(unit @ud) :: update available
yea=? :: update approved
==
::
+$ sync-update
$% [%new for=sync-record rev=@ud]
[%done for=sync-record rev=@ud]
[%drop for=sync-record rev=@ud]
[%pending pending=(set [for=sync-record rev=@ud])]
==
::
+$ sink (unit [her=@p sud=desk kid=(unit desk) let=@ud])
:: +truncate-hash: get last 5 digits of hash and convert to tape
::
++ truncate-hash
|= hash=@uv
^- tape
(slag 2 <`@uv`(mod hash 0v1.00000)>)
:: +report-prep: get data required for reports
::
++ report-prep
|= [our=@p now=@da]
=/ ego (scot %p our)
=/ wen (scot %da now)
:* .^(rock:tire %cx /[ego]//[wen]/tire)
.^(cone %cx /[ego]//[wen]/domes)
.^((map desk [ship desk]) %gx /[ego]/hood/[wen]/kiln/sources/noun)
.^ (map [desk ship desk] sync-state) %gx
/[ego]/hood/[wen]/kiln/syncs/noun
==
==
:: +report-vats: report on all desk installations
::
++ report-vats
|= [our=@p now=@da desks=(list desk) filt=@tas verb=?]
^- tang
=/ ego (scot %p our)
=/ wen (scot %da now)
=+ prep=[tyr cone sor zyn]=(report-prep our now)
?: =(%$ filt)
%- zing
%+ turn
?^ desks
(flop desks)
%+ sort ~(tap in ~(key by tyr.prep))
|= [a=desk b=desk]
?: |(=(a %kids) =(b %base)) &
?: |(=(a %base) =(b %kids)) |
(aor b a)
|=(syd=@tas (report-vat prep our now syd verb))
=/ deks=(list [=desk =zest wic=(set weft)])
?~ desks
%+ sort ~(tap by tyr.prep)
|= [[a=desk *] [b=desk *]]
?: |(=(a %kids) =(b %base)) &
?: |(=(a %base) =(b %kids)) |
(aor b a)
%+ murn (flop desks)
|= des=desk
^- (unit [=desk =zest wic=(set weft)])
?~ got=(~(get by tyr.prep) des)
~
`[des u.got]
?: =(filt %blocking)
=/ base-weft=(unit weft)
%- ~(rep in wic:(~(got by tyr.prep) %base))
|= [=weft out=(unit weft)]
?~ out
`weft
?: (lth num.weft num.u.out)
out
`weft
?~ base-weft ~['%base already up-to-date']
=/ blockers=(list desk)
%+ sort
^- (list desk)
%+ murn deks
|= [=desk =zest wic=(set weft)]
^- (unit ^desk)
?. =(%live zest)
~
?: (~(has in wic) u.base-weft)
~
`desk
aor
?~ blockers ~['No desks blocking upgrade']
%- zing
^- (list tang)
:- :~ %+ rap 3
:~ 'These desks are blocking upgrade to [%zuse '
(scot %ud num.u.base-weft)
']:'
== ==
%+ turn blockers
|=(syd=desk (report-vat prep our now syd verb))
::
%- zing
%+ turn
?+ filt !!
%exists
%+ skip deks
|= [syd=desk *]
?~ got=(~(get by cone.prep) our syd)
&
=(0 let.u.got)
::
%running
%+ skim deks
|=([* [zest=@tas *]] =(zest %live))
::
%suspended
%+ skip deks
|= [syd=@tas [zest=@tas *]]
?| =(syd %kids)
=(zest %live)
?~ got=(~(get by cone.prep) our syd)
&
=(0 let.u.got)
==
::
%exists-not
%+ skim deks
|= [syd=desk *]
?~ got=(~(get by cone.prep) our syd)
|
=(0 let.u.got)
==
|=([syd=desk *] (report-vat prep our now syd verb))
:: +report-vat: report on a single desk installation
::
++ report-vat
|= $: $: tyr=rock:tire =cone sor=(map desk (pair ship desk))
zyn=(map [desk ship desk] sync-state)
==
our=ship now=@da syd=desk verb=?
==
|^ ^- tang
=/ ego (scot %p our)
=/ wen (scot %da now)
?. ((sane %tas) syd)
~[(cat 3 'insane desk: %' syd)]
?. (~(has by cone) our syd)
~[(cat 3 'desk does not yet exist: %' syd)]
=/ hash .^(@uv %cz /[ego]/[syd]/[wen])
?: =(%kids syd)
~[(cat 3 '%kids %cz hash: ' (scot %uv hash))]
=/ kel-path /[ego]/[syd]/[wen]/sys/kelvin
?. .^(? %cu kel-path)
~[(cat 3 'bad desk: %' syd)]
=+ .^(=waft %cx kel-path)
^- tang
=/ =sink
?~ s=(~(get by sor) syd)
~
?~ z=(~(get by zyn) syd u.s)
~
`[p.u.s q.u.s [kid let]:u.z]
=/ meb=(list @uv)
?~ sink ~[hash]
%+ turn
.^ (list tako) %cs
/[ego]/[syd]/[wen]/base/(scot %p her.u.sink)/[sud.u.sink]
==
|=(=tako .^(@uv %cs /[ego]/[syd]/[wen]/hash/(scot %uv tako)))
=/ dek (~(got by tyr) syd)
=/ sat
?- zest.dek
%live 'running'
%dead 'suspended'
%held 'suspended until next update'
==
=/ kul=cord (print-wefts (waft-to-wefts waft))
?. verb
:~ '::'
(cat 3 ' pending updates: ' (print-wefts wic.dek))
(cat 3 ' source ship: ' ?~(sink '~' (scot %p her.u.sink)))
(cat 3 ' app status: ' sat)
(cat 3 ' %cz hash ends in: ' (print-shorthash hash))
(cat 3 ' /sys/kelvin: ' (print-wefts (waft-to-wefts waft)))
(cat 3 '%' syd)
==
::
=/ [on=(list @tas) of=(list @tas)]
=/ [on=(list @tas) of=(list @tas)]
%- ~(rep by ren:(~(got by cone) our syd))
|= [[=dude:gall is-on=?] on=(list @tas) of=(list @tas)]
?: is-on
[[dude on] of]
[on [dude of]]
[(sort on aor) (sort of aor)]
:~ '::'
(cat 3 ' pending updates: ' (print-wefts wic.dek))
%^ cat 3 ' kids desk: ' ?~ sink '~'
?~ kid.u.sink '~'
(cat 3 '%' u.kid.u.sink)
(cat 3 ' source aeon: ' ?~(sink '~' (scot %ud let.u.sink)))
(cat 3 ' source desk: ' ?~(sink '~' (cat 3 '%' sud.u.sink)))
(cat 3 ' source ship: ' ?~(sink '~' (scot %p her.u.sink)))
(cat 3 ' updates: ' ?~(sink 'local' 'remote'))
%^ cat 3 ' publishing ship: ' ?~ got=(get-publisher our syd now)
'~'
(scot %p u.got)
::
(cat 3 ' force off: ' (print-agents of))
(cat 3 ' force on: ' (print-agents on))
(cat 3 ' app status: ' sat)
::
(cat 3 ' %cz hash: ' (scot %uv hash))
(cat 3 ' base hash: ' (print-mergebases meb))
(cat 3 ' /sys/kelvin: ' (print-wefts (waft-to-wefts waft)))
(cat 3 '%' syd)
==
++ print-wefts
|= wefts=(set weft)
^- @t
?: =(~ wefts)
'~'
%+ roll (sort ~(tap in wefts) aor)
|= [=weft out=@t]
?: =('' out)
(rap 3 '[%' lal.weft ' ' (scot %ud num.weft) ']' ~)
(rap 3 out ' [%' lal.weft ' ' (scot %ud num.weft) ']' ~)
::
++ print-shorthash
|= hash=@uv
^- @t
(crip ((v-co:co 5) (end [0 25] hash)))
::
++ print-mergebases
|= hashes=(list @uv)
^- @t
?~ hashes
'~'
?~ t.hashes
(scot %uv i.hashes)
%+ roll `(list @uv)`hashes
|= [hash=@uv out=@t]
?: =('' out)
(print-shorthash hash)
(rap 3 out ' ' (print-shorthash hash) ~)
::
++ print-agents
|= agents=(list @tas)
^- @t
?~ agents
'~'
%+ roll `(list @tas)`agents
|= [agent=@tas out=@tas]
?: =('' out)
(cat 3 '%' agent)
(rap 3 out ' %' agent ~)
--
:: +report-kids: non-vat cz hash report for kids desk
::
++ report-kids
|= [our=ship now=@da]
^- tank
=/ syd %kids
=/ ego (scot %p our)
=/ wen (scot %da now)
?. (~(has in .^((set desk) %cd /[ego]//[wen])) syd)
'no %kids desk'
=+ .^(hash=@uv %cz /[ego]/[syd]/[wen])
(cat 3 '%kids %cz hash: ' (scot %uv hash))
:: +read-bill-foreign: read /desk/bill from a foreign desk
::
++ read-bill-foreign
|= [=ship =desk =aeon]
^- (list dude)
~| +<
=/ her (scot %p ship)
=/ syd (scot %tas desk)
=/ yon (scot %ud aeon)
::
=/ dom .^(domo cv/~[her syd yon])
=/ tak ~| aeons=~(key by hit.dom)
(scot %uv (~(got by hit.dom) aeon))
=/ yak .^(yaki cs/~[her syd yon %yaki tak])
=/ fil (~(get by q.yak) /desk/bill)
?~ fil ~
=/ lob (scot %uv u.fil)
=/ peg .^(page cs/~[her syd yon %blob lob])
;;((list dude) q.peg)
:: +read-bill: read contents of /desk/bill manifest
::
++ read-bill
|= [our=ship =desk now=@da]
=/ pax (en-beam [our desk da+now] /desk/bill)
?. .^(? cu/pax)
*(list dude)
.^((list dude) cx/pax)
::
++ get-remote-diff
|= [our=ship here=desk now=@da her=ship there=desk when=aeon]
=+ .^(our-hash=@uv cz/[(scot %p our) here (scot %da now) ~])
=+ .^(her-hash=@uv cz/[(scot %p her) there (scot %ud when) ~])
!=(our-hash her-hash)
::
++ get-publisher
|= [our=ship =desk now=@da]
^- (unit ship)
=/ pax /(scot %p our)/[desk]/(scot %da now)/desk/ship
?. .^(? %cu pax) ~
`.^(ship %cx pax)
::
++ get-apps-live
|= [our=ship =desk now=@da]
^- (list dude)
%+ murn (get-apps-have our desk now)
|=([=dude live=?] ?.(live ~ `dude))
:: +get-apps-have: find which apps Gall is running on a desk
::
++ get-apps-have
|= [our=ship =desk now=@da]
^- (list [=dude live=?])
%~ tap in
.^((set [=dude live=?]) ge+/(scot %p our)/[desk]/(scot %da now)/$)
::
++ mergebase-hashes
|= [our=@p syd=desk now=@da her=ship sud=desk]
=/ her (scot %p her)
=/ ego (scot %p our)
=/ wen (scot %da now)
%+ turn .^((list tako) %cs /[ego]/[syd]/[wen]/base/[her]/[sud])
|=(=tako .^(@uv %cs /[ego]/[syd]/[wen]/hash/(scot %uv tako)))
::
++ enjs
=, enjs:format
|%
++ tim
|= t=@
^- json
(numb (fall (mole |.((unm:chrono:userlib t))) 0))
::
++ cass
|= c=^cass
%- pairs
:~ ud+(numb ud.c)
da+(tim da.c)
==
::
++ weft
|= w=^weft
%- pairs
:~ name+s+lal.w
kelvin+(numb num.w)
==
::
++ rung
|= r=^rung
%- pairs
:~ aeon+(numb aeon.r)
weft+(weft weft.r)
==
--
--

27
desk/sur/spider.hoon Normal file
View File

@ -0,0 +1,27 @@
/+ libstrand=strand
=, strand=strand:libstrand
|%
+$ thread $-(vase shed:khan)
+$ input [=tid =cage]
+$ tid tid:strand
+$ bowl bowl:strand
+$ http-error
$? %bad-request :: 400
%forbidden :: 403
%nonexistent :: 404
%offline :: 504
==
+$ start-args
$: parent=(unit tid)
use=(unit tid)
=beak
file=term
=vase
==
+$ inline-args
$: parent=(unit tid)
use=(unit tid)
=beak
=shed:khan
==
--

46
desk/sur/sss.hoon Normal file
View File

@ -0,0 +1,46 @@
::
:: XX remove this file in transition to %4
::
|%
++ lake
|$ [rock wave]
$_ ^?
|%
++ name *term
+$ rock ^rock
+$ wave ^wave
++ wash |~ [rock wave] *rock
--
+$ aeon @ud
+$ dude dude:agent:gall
+$ what ?(%rock %wave)
++ poke
|%
++ request
|* paths=mold
$: path=paths
=dude
when=(unit aeon)
==
++ response
|* [=(lake) paths=mold]
$: path=paths
=dude
=aeon
$% [type=?(%nigh %yore %tomb) ~]
$: type=%scry
$% [what=%rock =rock:lake]
[what=%wave =wave:lake]
== == == ==
++ on-rock
|* [=(lake) paths=mold]
$: path=paths
src=ship
from=dude
stale=?
fail=?
=rock:lake
wave=(unit wave:lake)
==
--
--

464
desk/sur/tlon/channels.hoon Normal file
View File

@ -0,0 +1,464 @@
:: channels: message stream structures
::
:: four shapes that cross client-subscriber-publisher boundaries:
:: - actions client-to-subscriber change requests (user actions)
:: - commands subscriber-to-publisher change requests
:: - updates publisher-to-subscriber change notifications
:: - responses subscriber-to-client change notifications
::
:: --action--> --command-->
:: client subscriber publisher
:: <--response-- <--update--
::
:: local actions _may_ become responses,
:: remote actions become commands,
:: commands _may_ become updates,
:: updates _may_ become responses.
::
/- g=tlon-groups, c=tlon-cite
/+ mp=mop-extensions
|%
+| %ancients
::
++ mar
|%
++ act `mark`%channel-action
++ cmd `mark`%channel-command
++ upd `mark`%channel-update
++ log `mark`%channel-logs
++ not `mark`%channel-posts
--
::
+| %primitives
::
+$ flag (pair ship term)
+$ v-channels (map nest v-channel)
++ v-channel
|^ ,[global local]
:: $global: should be identical between ships
::
+$ global
$: posts=v-posts
order=(rev order=arranged-posts)
view=(rev =view)
sort=(rev =sort)
perm=(rev =perm)
==
:: $window: sparse set of time ranges
::
::TODO populate this
+$ window (list [from=time to=time])
:: .window: time range for requested posts that we haven't received
:: .diffs: diffs for posts in the window, to apply on receipt
::
+$ future
[=window diffs=(jug id-post u-post)]
:: $local: local-only information
::
+$ local
$: =net
=log
=remark
=window
=future
==
--
:: $v-post: a channel post
::
+$ v-post [v-seal (rev essay)]
+$ id-post time
+$ v-posts ((mop id-post (unit v-post)) lte)
++ on-v-posts ((on id-post (unit v-post)) lte)
++ mo-v-posts ((mp id-post (unit v-post)) lte)
:: $v-reply: a post comment
::
+$ v-reply [v-reply-seal (rev memo)]
+$ id-reply time
+$ v-replies ((mop id-reply (unit v-reply)) lte)
++ on-v-replies ((on id-reply (unit v-reply)) lte)
++ mo-v-replies ((mp time (unit v-reply)) lte)
:: $v-seal: host-side data for a post
::
+$ v-seal $+ channel-seal
$: id=id-post
replies=v-replies
reacts=v-reacts
==
:: $v-reply-seal: host-side data for a reply
::
+$ v-reply-seal
$: id=id-reply
reacts=v-reacts
==
:: $essay: top-level post, with metadata
::
+$ essay [memo =kind-data]
:: $reply-meta: metadata for all replies
+$ reply-meta
$: reply-count=@ud
last-repliers=(set ship)
last-reply=(unit time)
==
:: $kind-data: metadata for a channel type's "post"
::
+$ kind-data
$% [%diary title=@t image=@t]
[%heap title=(unit @t)]
[%chat kind=$@(~ [%notice ~])]
==
:: $memo: post data proper
::
:: content: the body of the comment
:: author: the ship that wrote the comment
:: sent: the client-side time the comment was made
::
+$ memo
$: content=story
author=ship
sent=time
==
:: $story: post body content
::
+$ story (list verse)
:: $verse: a chunk of post content
::
:: blocks stand on their own. inlines come in groups and get wrapped
:: into a paragraph
::
+$ verse
$% [%block p=block]
[%inline p=(list inline)]
==
:: $listing: recursive type for infinitely nested <ul> or <ol>
+$ listing
$% [%list p=?(%ordered %unordered %tasklist) q=(list listing) r=(list inline)]
[%item p=(list inline)]
==
:: $block: post content that sits outside of the normal text
::
:: %image: a visual, we record dimensions for better rendering
:: %cite: an Urbit reference
:: %header: a traditional HTML heading, h1-h6
:: %listing: a traditional HTML list, ul and ol
:: %code: a block of code
::
+$ block $+ channel-block
$% [%image src=cord height=@ud width=@ud alt=cord]
[%cite =cite:c]
[%header p=?(%h1 %h2 %h3 %h4 %h5 %h6) q=(list inline)]
[%listing p=listing]
[%rule ~]
[%code code=cord lang=cord]
==
:: $inline: post content that flows within a paragraph
::
:: @t: plain text
:: %italics: italic text
:: %bold: bold text
:: %strike: strikethrough text
:: %inline-code: code formatting for small snippets
:: %blockquote: blockquote surrounded content
:: %block: link/reference to blocks
:: %code: code formatting for large snippets
:: %tag: tag gets special signifier
:: %link: link to a URL with a face
:: %break: line break
::
+$ inline $+ channel-inline
$@ @t
$% [%italics p=(list inline)]
[%bold p=(list inline)]
[%strike p=(list inline)]
[%blockquote p=(list inline)]
[%inline-code p=cord]
[%code p=cord]
[%ship p=ship]
[%block p=@ud q=cord]
[%tag p=cord]
[%link p=cord q=cord]
[%task p=?(%.y %.n) q=(list inline)]
[%break ~]
==
::
+$ kind ?(%diary %heap %chat)
:: $nest: identifier for a channel
+$ nest [=kind =ship name=term]
:: $view: the persisted display format for a channel
+$ view $~(%list ?(%grid %list))
:: $sort: the persisted sort type for a channel
+$ sort $~(%time ?(%alpha %time %arranged))
:: $arranged-posts: an array of postIds
+$ arranged-posts (unit (list time))
:: $hidden-posts: a set of ids for posts that are hidden
+$ hidden-posts (set id-post)
:: $post-toggle: hide or show a particular post by id
+$ post-toggle
$% [%hide =id-post]
[%show =id-post]
==
:: $react: either an emoji identifier like :diff or a URL for custom
+$ react @ta
+$ v-reacts (map ship (rev (unit react)))
:: $scan: search results
+$ scan (list reference)
+$ reference
$% [%post =post]
[%reply =id-post =reply]
==
:: $said: used for references
+$ said (pair nest reference)
:: $plan: index into channel state
:: p: Post being referred to
:: q: Reply being referred to, if any
::
+$ plan
(pair time (unit time))
::
:: $net: subscriber-only state
::
+$ net [p=ship load=_|]
::
:: $unreads: a map of channel unread information, for clients
:: $unread: unread data for a specific channel, for clients
:: recency: time of most recent message
:: count: how many posts are unread
:: unread-id: the id of the first unread top-level post
:: threads: for each unread thread, the id of the first unread reply
::
+$ unreads (map nest unread)
+$ unread
$: recency=time
count=@ud
unread-id=(unit id-post)
threads=(map id-post id-reply)
==
:: $remark: markers representing unread state
:: last-read: time at which the user last read this channel
:: watching: unused, intended for disabling unread accumulation
:: unread-threads: threads that contain unread messages
::
+$ remark [recency=time last-read=time watching=_| unread-threads=(set id-post)]
::
:: $perm: represents the permissions for a channel and gives a
:: pointer back to the group it belongs to.
::
+$ perm
$: writers=(set sect:g)
group=flag:g
==
::
:: $log: a time ordered history of modifications to a channel
::
+$ log ((mop time u-channel) lte)
++ log-on ((on time u-channel) lte)
::
:: $create-channel: represents a request to create a channel
::
:: $create-channel is consumed by the channel agent first and then
:: passed to the groups agent to register the channel with the group.
::
:: Write permission is stored with the specific agent in the channel,
:: read permission is stored with the group's data.
::
+$ create-channel
$: =kind
name=term
group=flag:g
title=cord
description=cord
readers=(set sect:g)
writers=(set sect:g)
==
:: $outline: abridged $post
:: .replies: number of comments
::
+$ outline
[replies=@ud replyers=(set ship) essay]
::
++ outlines
=< outlines
|%
+$ outlines ((mop time outline) lte)
++ on ((^on time outline) lte)
--
++ rev
|$ [data]
[rev=@ud data]
::
++ apply-rev
|* [old=(rev) new=(rev)]
^+ [changed=& old]
?: (lth rev.old rev.new)
&+new
|+old
::
++ next-rev
|* [old=(rev) new=*]
^+ [changed=& old]
?: =(+.old new)
|+old
&+old(rev +(rev.old), + new)
::
+| %actions
::
:: some actions happen to be the same as commands, but this can freely
:: change
::
::NOTE we might want to add a action-id=uuid to this eventually, threading
:: that through all the way, so that an $r-channels may indicate what
:: originally caused it
+$ a-channels
$% [%create =create-channel]
[%pin pins=(list nest)]
[%channel =nest =a-channel]
[%toggle-post toggle=post-toggle]
==
+$ a-channel
$% [%join group=flag:g]
[%leave ~]
a-remark
c-channel
==
::
+$ a-remark
$~ [%read ~]
$% [%read ~]
[%read-at =time]
[%watch ~]
[%unwatch ~]
==
::
+$ a-post c-post
+$ a-reply c-reply
::
+| %commands
::
+$ c-channels
$% [%create =create-channel]
[%channel =nest =c-channel]
==
+$ c-channel
$% [%post =c-post]
[%view =view]
[%sort =sort]
[%order order=arranged-posts]
[%add-writers sects=(set sect:g)]
[%del-writers sects=(set sect:g)]
==
::
+$ c-post
$% [%add =essay]
[%edit id=id-post =essay]
[%del id=id-post]
[%reply id=id-post =c-reply]
c-react
==
::
+$ c-reply
$% [%add =memo]
[%edit id=id-reply =memo]
[%del id=id-reply]
c-react
==
::
+$ c-react
$% [%add-react id=@da p=ship q=react]
[%del-react id=@da p=ship]
==
::
+| %updates
::
+$ update [=time =u-channel]
+$ u-channels [=nest =u-channel]
+$ u-channel
$% [%create =perm]
[%order (rev order=arranged-posts)]
[%view (rev =view)]
[%sort (rev =sort)]
[%perm (rev =perm)]
[%post id=id-post =u-post]
==
::
+$ u-post
$% [%set post=(unit v-post)]
[%reacts reacts=v-reacts]
[%essay (rev =essay)]
[%reply id=id-reply =u-reply]
==
::
+$ u-reply
$% [%set reply=(unit v-reply)]
[%reacts reacts=v-reacts]
==
::
+$ u-checkpoint global:v-channel
::
+| %responses
::
+$ r-channels [=nest =r-channel]
+$ r-channel
$% [%posts =posts]
[%post id=id-post =r-post]
[%order order=arranged-posts]
[%view =view]
[%sort =sort]
[%perm =perm]
::
[%create =perm]
[%join group=flag:g]
[%leave ~]
a-remark
==
::
+$ r-post
$% [%set post=(unit post)]
[%reply id=id-reply =reply-meta =r-reply]
[%reacts =reacts]
[%essay =essay]
==
::
+$ r-reply
$% [%set reply=(unit reply)]
[%reacts =reacts]
==
:: versions of backend types with their revision numbers stripped,
:: because the frontend shouldn't care to learn those.
::
+$ channels (map nest channel)
++ channel
|^ ,[global local]
+$ global
$: =posts
order=arranged-posts
=view
=sort
=perm
==
::
+$ local
$: =net
=remark
==
--
+$ paged-posts
$: =posts
newer=(unit time)
older=(unit time)
total=@ud
==
+$ posts ((mop id-post (unit post)) lte)
+$ post [seal essay]
+$ seal
$: id=id-post
=reacts
=replies
=reply-meta
==
+$ reacts (map ship react)
+$ reply [reply-seal memo]
+$ replies ((mop id-reply reply) lte)
+$ reply-seal [id=id-reply parent-id=id-post =reacts]
++ on-posts ((on id-post (unit post)) lte)
++ on-replies ((on id-reply reply) lte)
+$ cite cite:c
--

57
desk/sur/tlon/cite.hoon Normal file
View File

@ -0,0 +1,57 @@
/- g=tlon-groups
=< cite
|%
++ purse
|= =(pole knot)
^- (unit cite)
?. =(~.1 -.pole) ~
=. pole +.pole
?+ pole ~
[%chan agent=@ ship=@ name=@ rest=*]
=/ ship (slaw %p ship.pole)
?~ ship ~
`[%chan [agent.pole u.ship name.pole] rest.pole]
::
[%desk ship=@ name=@ rest=*]
=/ ship (slaw %p ship.pole)
?~ ship ~
`[%desk [u.ship name.pole] rest.pole]
::
[%group ship=@ name=@ ~]
=/ ship (slaw %p ship.pole)
?~ ship ~
`[%group u.ship name.pole]
==
::
++ parse
|= =path
^- cite
(need (purse path))
::
++ print
|= c=cite
|^ ^- path
:- (scot %ud 1)
?- -.c
%chan chan/(welp (nest nest.c) wer.c)
%desk desk/(welp (flag flag.c) wer.c)
%group group/(flag flag.c)
%bait bait/:(welp (flag grp.c) (flag gra.c) wer.c)
==
++ flag
|= f=flag:g
~[(scot %p p.f) q.f]
++ nest
|= n=nest:g
[p.n (flag q.n)]
--
::
+$ cite
$% [%chan =nest:g wer=path]
[%group =flag:g]
[%desk =flag:g wer=path]
[%bait grp=flag:g gra=flag:g wer=path]
:: scry into groups when you receive a bait for a chat that doesn't exist yet
:: work out what app
==
--

384
desk/sur/tlon/groups.hoon Normal file
View File

@ -0,0 +1,384 @@
|%
+$ kind ?(%diary %heap %chat)
++ epic
|%
+$ saga
$~ [%lev ~]
$% [%dex ver=@ud]
[%lev ~]
[%chi ~]
==
+$ epic @ud
--
++ meta
|%
+$ data
$: title=cord
description=cord
image=cord
cover=cord
==
+$ diff
$% [%title =cord]
[%description =cord]
[%image =cord]
[%cover =cord]
==
--
++ e epic
++ okay `epic:e`2
++ mar
|%
++ act `mark`(rap 3 %group-action '-' (scot %ud okay) ~)
++ upd `mark`(rap 3 %group-update '-' (scot %ud okay) ~)
++ log `mark`(rap 3 %group-log '-' (scot %ud okay) ~)
++ int `mark`(rap 3 %group-init '-' (scot %ud okay) ~)
--
:: $flag: ID for a group
::
+$ flag (pair ship term)
::
:: $nest: ID for a channel, {app}/{ship}/{name}
::
+$ nest (pair term flag)
::
:: $sect: ID for cabal, similar to a role
::
+$ sect term
::
:: $zone: channel grouping
::
:: includes its own metadata for display and keeps the order of
:: channels within.
::
:: zone: the term that represents the ID of a zone
:: realm: the metadata representing the zone and the order of channels
:: delta: the set of actions that can be taken on a zone
:: %add: create a zone
:: %del: delete the zone
:: %edit: modify the zone metadata
:: %mov: reorders the zone in the group
:: %mov-nest: reorders a channel within the zone
::
++ zone
=< zone
|%
+$ zone @tas
+$ realm
$: met=data:meta
ord=(list nest)
==
+$ diff (pair zone delta)
+$ delta
$% [%add meta=data:meta]
[%del ~]
[%edit meta=data:meta]
[%mov idx=@ud]
[%mov-nest =nest idx=@ud]
==
--
::
:: $fleet: group members and their associated metadata
::
:: vessel: a user's set of sects or roles and the time that they joined
:: @da default represents an admin added member that has yet to join
::
++ fleet
=< fleet
|%
+$ fleet (map ship vessel)
+$ vessel
$: sects=(set sect)
joined=time
==
+$ diff
$% [%add ~]
[%del ~]
[%add-sects sects=(set sect)]
[%del-sects sects=(set sect)]
==
--
::
:: $channel: a medium for interaction
::
++ channel
=< channel
|%
+$ preview
$: =nest
meta=data:meta
group=^preview
==
::
+$ channels (map nest channel)
::
:: $channel: a collection of metadata about a specific agent integration
::
:: meta: title, description, image, cover
:: added: when the channel was created
:: zone: what zone or section to bucket in
:: join: should the channel be joined by new members
:: readers: what sects can see the channel, empty means anyone
::
+$ channel
$: meta=data:meta
added=time
=zone
join=?
readers=(set sect)
==
::
:: $diff: represents the set of actions you can take on a channel
::
:: add: create a channel
:: edit: edit a channel
:: del: delete a channel
:: add-sects: add sects to readers
:: del-sects: delete sects from readers
:: zone: change the zone of the channel
:: join: toggle default join
::
+$ diff
$% [%add =channel]
[%edit =channel]
[%del ~]
::
[%add-sects sects=(set sect)]
[%del-sects sects=(set sect)]
::
[%zone =zone]
::
[%join join=_|]
==
--
::
:: $group: collection of people and the pathways in which they interact
::
:: group holds all data around members, permissions, channel
:: organization, and its own metadata to represent the group
::
+$ group
$: =fleet
cabals=(map sect cabal)
zones=(map zone realm:zone)
zone-ord=(list zone)
=bloc
=channels:channel
imported=(set nest)
=cordon
secret=?
meta=data:meta
==
::
+$ group-ui [group saga=(unit saga:e)]
:: $cabal: metadata representing a $sect or role
::
++ cabal
=< cabal
|%
::
+$ cabal
[meta=data:meta ~]
::
+$ diff
$% [%add meta=data:meta]
[%edit meta=data:meta]
[%del ~]
==
--
::
:: $cordon: group entry and visibility permissions
::
++ cordon
=< cordon
|%
::
:: $open: a group with open entry, only bans are barred entry
::
++ open
|%
:: $ban: set of ships and ranks/classes that are not allowed entry
::
:: bans can either be done at the individual ship level or by the
:: rank level (comet/moon/etc.)
::
+$ ban [ships=(set ship) ranks=(set rank:title)]
+$ diff
$% [%add-ships p=(set ship)]
[%del-ships p=(set ship)]
::
[%add-ranks p=(set rank:title)]
[%del-ranks p=(set rank:title)]
==
--
::
:: $shut: a group with closed entry, everyone barred entry
::
:: a shut cordon means that the group is closed, but still visible.
:: people may request entry and either be accepted or denied or
:: they may be invited directly
::
:: ask: represents those requesting entry
:: pending: represents those who've been invited
::
++ shut
|%
+$ state [pend=(set ship) ask=(set ship)]
+$ kind ?(%ask %pending)
+$ diff
$% [%add-ships p=kind q=(set ship)]
[%del-ships p=kind q=(set ship)]
==
--
::
:: $cordon: a set of metadata to represent the entry policy for a group
::
:: open: a group with open entry, only bans barred entry
:: shut: a group with closed entry, everyone barred entry
:: afar: a custom entry policy defined by another agent
::
+$ cordon
$% [%shut state:shut]
[%afar =flag =path desc=@t]
[%open =ban:open]
==
::
:: $diff: the actions you can take on a cordon
::
+$ diff
$% [%shut p=diff:shut]
[%open p=diff:open]
[%swap p=cordon]
==
--
::
:: $bloc: superuser sects
::
:: sects in the bloc set are allowed to make modifications to the group
:: and its various metadata and permissions
::
++ bloc
=< bloc
|%
+$ bloc (set sect)
+$ diff
$% [%add p=(set sect)]
[%del p=(set sect)]
==
--
::
:: $diff: the general set of changes that can be made to a group
::
+$ diff
$% [%fleet p=(set ship) q=diff:fleet]
[%cabal p=sect q=diff:cabal]
[%channel p=nest q=diff:channel]
[%bloc p=diff:bloc]
[%cordon p=diff:cordon]
[%zone p=diff:zone]
[%meta p=data:meta]
[%secret p=?]
[%create p=group]
[%del ~]
==
::
:: $action: the complete set of data required to edit a group
::
+$ action
(pair flag update)
::
:: $update: a representation in time of a modification of a group
::
+$ update
(pair time diff)
::
:: $create: a request to make a group
::
+$ create
$: name=term
title=cord
description=cord
image=cord
cover=cord
=cordon
members=(jug ship sect)
secret=?
==
::
+$ init [=time =group]
::
:: $groups-ui: map for frontend to display groups
+$ groups-ui
(map flag group-ui)
+$ groups
(map flag group)
+$ net-groups
(map flag [net group])
::
:: $log: a time ordered map of all modifications to groups
::
+$ log
((mop time diff) lte)
::
++ log-on
((on time diff) lte)
::
:: $net: an indicator of whether I'm a host or subscriber
::
+$ net
$~ [%pub ~]
$% [%pub p=log]
[%sub p=time load=_| =saga:e]
==
::
:: $join: a join request, can elect to join all channels
::
+$ join
$: =flag
join-all=?
==
::
:: $knock: a request to enter a closed group
::
+$ knock flag
::
:: $progress: the state of a group join
::
+$ progress
?(%knocking %adding %watching %done %error)
::
:: $claim: a mark for gangs to represent a join in progress
::
+$ claim
$: join-all=?
=progress
==
::
:: $preview: the metadata and entry policy for a group
::
+$ preview
$: =flag
meta=data:meta
=cordon
=time
secret=?
==
::
+$ previews (map flag preview)
::
:: $invite: a marker to show you've been invited to a group
::
+$ invite (pair flag ship)
::
:: $gang: view of foreign group
::
+$ gang
$: cam=(unit claim)
pev=(unit preview)
vit=(unit invite)
==
::
+$ gangs (map flag gang)
::
--

52
desk/sur/ucm.hoon Normal file
View File

@ -0,0 +1,52 @@
/- t=tlon-groups, docket
/+ metalib=metamask
|%
::
+$ state state-0
+$ state-0
$: %0
=sites
sessions=sess:metalib
==
+$ sites (map path site)
+$ site
$: sitename=@t
description=@t
groupname=@tas
binding=path
icon=@t
home=@t
css=@t
=apps
=app-order
hidden=_|
==
+$ app-order (list navbar-app)
+$ navbar-app $@(app-type [%static @tas])
+$ apps
$: blog=@t
radio=?
chat=(set term)
forum=(set term)
wiki=@t
static=(map @tas @t) :: markdown
==
+$ filedata [html=@t md=@t theme=@tas public=?]
:: %blog-action
+$ sail-poke [%ui ship=@p eyre-id=@ta =action]
+$ action
:: global
$% [%dash dash-action]
[%site name=@t action=site-action]
==
+$ dash-action
$% [%set site]
[%del name=path]
==
+$ site-action
$%
[%search query=@t start=(unit @da) end=(unit @da) apps=(list app-type) by=(unit @p)]
[%lol @t]
==
+$ app-type $?(%blog %chat %forum %radio %wiki)
--

1
desk/sys.kelvin Normal file
View File

@ -0,0 +1 @@
[%zuse 411]

85
desk/ted/import.hoon Normal file
View File

@ -0,0 +1,85 @@
/- spider, blog
/+ *strandio
=, strand=strand:spider
=, strand-fail=strand-fail:libstrand:spider
^- thread:spider
|= arg=vase
=/ m (strand ,vase)
^- form:m
;< our=@p bind:m get-our
;< now=@da bind:m get-time
=/ files-path=path /(scot %p our)/blog/(scot %da now)/import
=/ files=(axal *)
|-
=+ .^(=arch %cy files-path)
?: (test dir.arch ~)
=/ mark
?+ (rear files-path) !!
%md wain
%html @t
%css @t
%noun noun
==
:- [~ .^(mark %cx files-path)]
(~(urn by dir.arch) |=([name=@ta ~] ^$(files-path (snoc files-path name))))
:- fil.arch
(~(urn by dir.arch) |=([name=@ta ~] ^$(files-path (snoc files-path name))))
::
=| $= imp
$: pub=(map path [html=@t md=@t theme=@tas])
dra=(map path md=@t)
thm=(map @tas css=@t)
==
=. imp
=/ all-files ~(tap of files)
|-
?~ all-files imp
%. i.all-files
|= [=path content=*]
%= ^$
all-files t.all-files
imp
?+ (rear path) imp
%md
=+ content=(of-wain:format ((list cord) content))
?: ?=(%published (snag 0 path))
=/ file-name (oust [0 2] (snip path))
=/ file (~(gut by pub.imp) file-name [html='' md='' theme=%default])
imp(pub (~(put by pub.imp) file-name file(md content)))
=/ draft-name (oust [0 1] (snip path))
=/ draft (~(gut by dra.imp) draft-name [md=''])
imp(dra (~(put by dra.imp) draft-name draft(md content)))
::
%css
=/ theme-name (@tas (rear (oust [0 1] (snip path))))
=/ theme (~(gut by thm.imp) theme-name [css=''])
imp(thm (~(put by thm.imp) theme-name theme(css (@t (@tas content)))))
::
%html
=/ file-name (oust [0 2] (snip path))
=/ file (~(gut by pub.imp) file-name [html='' md='' theme=%default])
imp(pub (~(put by pub.imp) file-name file(html (@t (@tas content)))))
==
==
=/ thm-map ;; (map path @tas)
:: XX is this expecting a folder called 'meta' in the export?
:: there wasn't one when i exported files from ~bonbud-macryg,
:: but that might be because the export process crashed my ship
.^(noun %cx /(scot %p our)/blog/(scot %da now)/import/published/meta/noun)
=/ act |=(=vase [%pass /poke %agent [our %blog] %poke %blog-action vase])
;< ~ bind:m
%- send-raw-cards
%- zing
:~ %+ turn ~(tap by pub.imp)
|= [=path html=@t md=@t theme=@tas]
(act !>([%publish path html md (~(gut by thm-map) path %default)]))
::
%+ turn ~(tap by dra.imp)
|= [=path md=@t]
(act !>([%save-draft path md]))
::
%+ turn ~(tap by thm.imp)
|= [theme=@tas css=@t]
(act !>([%save-theme theme css]))
==
(pure:m !>(~))

26
desk/ted/iris-thread.hoon Normal file
View File

@ -0,0 +1,26 @@
/- spider
/+ strandio
=, strand=strand:spider
^- thread:spider
|= arg=vase
=/ m (strand ,vase)
^- form:m
~& "calling iris thread"
=/ req !<([@t request:http] arg)
=/ [channel-slug=@t =request:http] req
~& >> request
=/ origin (get-header:http 'origin' header-list.request)
?~ origin (pure:m !>(~))
=/ url (cat 3 [u.origin channel-slug])
~& >> `@t`url
=. url.request url
=/ =task:iris [%request request *outbound-config:iris]
=/ =card:agent:gall [%pass /http-req %arvo %i task]
;< ~ bind:m (send-raw-card:strandio card)
;< res=(pair wire sign-arvo) bind:m take-sign-arvo:strandio
?. ?=([%iris %http-response %finished *] q.res)
(strand-fail:strand %bad-sign ~)
~& +.q.res
?~ full-file.client-response.q.res
(strand-fail:strand %no-body ~)
(pure:m !>(`@t`q.data.u.full-file.client-response.q.res))

1
desk/web Symbolic link
View File

@ -0,0 +1 @@
../site/dist

122
devenv.lock Normal file
View File

@ -0,0 +1,122 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1726063457,
"owner": "cachix",
"repo": "devenv",
"rev": "39bf6ce569103c9390d37322daa59468c31b3ce7",
"treeHash": "839747a1cb35ba6d5b36cce9a739ab2ba5e4a5d4",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1716977621,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1725930920,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "44a71ff39c182edaf25a7ace5c9454e7cba2c658",
"treeHash": "56e93544112b7bb7aa0c3093d537295683ef9148",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1725513492,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "7570de7b9b504cfe92025dd1be797bf546f66528",
"treeHash": "4b46d77870afecd8f642541cb4f4927326343b59",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}

52
devenv.nix Normal file
View File

@ -0,0 +1,52 @@
{
pkgs,
lib,
config,
inputs,
...
}: {
# https://devenv.sh/basics/
env.GREET = "devenv";
# https://devenv.sh/packages/
packages = with pkgs; [
git
nodePackages.typescript-language-server
nodePackages.vscode-json-languageserver
nodePackages.prettier
];
# https://devenv.sh/languages/
# languages.rust.enable = true;
languages.javascript = {
enable = true;
bun.enable = true;
};
# https://devenv.sh/processes/
# processes.cargo-watch.exec = "cargo-watch";
# https://devenv.sh/services/
# services.postgres.enable = true;
# https://devenv.sh/scripts/
scripts.hello.exec = ''
echo hello from $GREET
'';
enterShell = ''
hello
git --version
'';
# https://devenv.sh/tests/
enterTest = ''
echo "Running tests"
git --version | grep --color=auto "${pkgs.git.version}"
'';
# https://devenv.sh/pre-commit-hooks/
# pre-commit.hooks.shellcheck.enable = true;
# See full reference at https://devenv.sh/reference/options/
}

14
devenv.yaml Normal file
View File

@ -0,0 +1,14 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend

24
fe/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

7
fe/.prettierrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 90
}

50
fe/README.md Normal file
View File

@ -0,0 +1,50 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
});
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react';
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
});
```

BIN
fe/bun.lockb Executable file

Binary file not shown.

25
fe/eslint.config.js Normal file
View File

@ -0,0 +1,25 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
);

21
fe/index.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="mask-icon" href="/favicon.ico" color="#ffffff">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/android-chrome-512x512.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Urbit Community Manager</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

56
fe/package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "ucm",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/inter": "^5.1.0",
"@fontsource/roboto": "^5.1.0",
"@hello-pangea/dnd": "^17.0.0",
"@mui/icons-material": "^6.1.0",
"@mui/material": "^6.1.0",
"@urbit/http-api": "file:../../js-http-api",
"any-ascii": "^0.3.2",
"bn.js": "^5.2.1",
"invariant": "^2.2.4",
"lodash": "^4.17.21",
"marked": "^14.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-html-parser": "^2.0.2",
"react-player": "^2.16.0",
"svgson": "^5.3.1",
"transformation-matrix": "^2.16.1",
"wouter": "^3.3.5",
"zustand": "^4.5.5"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@faker-js/faker": "^9.0.3",
"@types/bn.js": "^5.1.6",
"@types/invariant": "^2.2.37",
"@types/lodash": "^4.17.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
fe/public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
fe/public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
fe/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

72
fe/src/App.css Normal file
View File

@ -0,0 +1,72 @@
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
*{
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
height: 100%;
overflow: hidden;
/* max-width: 1280px; */
/* margin: 0 auto; */
/* padding: 2rem; */
/* text-align: center; */
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
em {
margin: 0 0.5ch;
}
.textarea textarea, .textarea >div{
width: 100%;
height: 100% !important;
}
.textarea textarea{
overflow-y: scroll !important;
}
a {
font-weight: 500;
text-decoration: inherit;
}

24
fe/src/App.tsx Normal file
View File

@ -0,0 +1,24 @@
import { useEffect, useState } from 'react';
import './App.css';
import useStore from './logic/store';
import Router from './Router';
import { LoadingScreen } from './ui/Components';
import { createTheme, ThemeProvider, styled, PaletteMode } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import AppTheme from './ui/shared-theme/AppTheme';
function App() {
// data loading
const { init, loading } = useStore(['init', 'loading']);
useEffect(() => {
init();
}, []);
return (
<AppTheme>
<CssBaseline enableColorScheme />
{loading ? <LoadingScreen /> : <Router />}
</AppTheme>
);
}
export default App;

48
fe/src/Router.tsx Normal file
View File

@ -0,0 +1,48 @@
import { Link, Route, Router } from "wouter";
import Dashboard from "./dashboard/Dashboard.tsx";
import CreateForm from "./dashboard/CreateForm.tsx";
import SiteDash from "./dashboard/Site.tsx";
import Modal from "./modals/Modal.tsx";
import useStore from "./logic/store.ts";
import { Button, Container, Typography } from "@mui/material";
export default function () {
const { airlock, state, groups, modal } = useStore([
"airlock",
"state",
"groups",
"modal",
]);
return (
<>
<Router base="/apps/ucm">
<Route path="/" component={Dashboard} />
<Route path="/create-site" component={CreateForm} />
<Route path="/admin/:site">
{(params) => {
const binding = `/${params.site}`;
const site = state[binding];
if (!site) return <NotFound err={params.site} />;
else {
const groupParam = `~${airlock.ship}/${site.groupname}`;
const group = groups[groupParam];
return <SiteDash site={site} group={group} />;
}
}}
</Route>
</Router>
{modal && <Modal>{modal}</Modal>}
</>
);
}
export function NotFound({ err }: { err: string }) {
return (
<Container maxWidth="sm" sx={{ mt: 4, textAlign: "center" }}>
<Typography>{err} not found</Typography>
<Link to="/">
<Button>Go Back</Button>
</Link>
</Container>
);
}

View File

@ -0,0 +1,23 @@
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="width: 256px; height: 256px; opacity: 1;" xml:space="preserve">
<g>
<path class="st0" d="M503.694,7.871c1.344-2.047,1.047-4.781-0.734-6.453c-1.781-1.703-4.5-1.891-6.484-0.438l-98.547,71.344
c-1.891,1.359-4.484,1.266-6.266-0.25s-2.313-4.047-1.266-6.141l10.844-21.656c1.031-2.047,0.547-4.547-1.156-6.063
c-1.719-1.531-4.25-1.719-6.172-0.469L83.006,230.137c-3.156,1.813-6.297,3.688-9.328,5.75l-0.25,0.156l0.016,0.016
c-7.625,5.219-14.922,11.125-21.688,17.891c-59.047,59.031-59.047,154.75-0.016,213.766
c29.531,29.516,68.219,44.281,106.906,44.281s77.359-14.766,106.891-44.281c6.766-6.766,12.656-14.047,17.875-21.672v0.016
l0.109-0.172c1.844-2.703,3.516-5.484,5.172-8.281l188.375-302.078c1.203-1.844,1.094-4.25-0.281-5.953
c-1.375-1.734-3.688-2.375-5.75-1.594l-46.829,17.563c-2.063,0.75-4.375,0.125-5.75-1.594s-1.484-4.125-0.266-5.953L503.694,7.871z
M158.647,464.73c-27.766,0-53.859-10.797-73.484-30.438c-40.5-40.5-40.5-106.406,0-146.922
c6.813-6.797,14.422-12.469,22.578-17.063c7.406-4.172,15.297-7.391,23.5-9.641c0.766-0.203,1.547-0.375,2.344-0.578
c3.172-0.797,6.406-1.422,9.672-1.906c1.031-0.156,2.047-0.328,3.078-0.453c4.047-0.484,8.156-0.797,12.313-0.797
c27.75,0,53.828,10.813,73.453,30.438c2.344,2.328,4.516,4.781,6.578,7.281c0.688,0.813,1.297,1.672,1.938,2.5
c1.344,1.734,2.641,3.469,3.859,5.25c0.703,1.031,1.359,2.063,2.016,3.109c1.047,1.656,2.047,3.344,3,5.063
c0.609,1.109,1.219,2.234,1.797,3.359c0.859,1.719,1.656,3.484,2.422,5.234c0.5,1.141,1.031,2.266,1.484,3.422
c0.813,2.063,1.516,4.172,2.188,6.266c0.656,2.031,1.219,4.063,1.75,6.125c0.391,1.563,0.813,3.109,1.141,4.688
c0.344,1.703,0.609,3.438,0.875,5.156c0.188,1.219,0.375,2.438,0.516,3.656c0.219,1.875,0.391,3.75,0.5,5.625
c0.063,1.016,0.109,2.047,0.156,3.063c0.063,2.047,0.109,4.094,0.047,6.141c-0.016,0.656-0.063,1.328-0.094,1.984
c-0.453,10.406-2.438,20.766-6,30.688c-0.063,0.219-0.141,0.438-0.219,0.641c-0.891,2.422-1.875,4.813-2.938,7.188
c-0.125,0.266-0.266,0.563-0.391,0.828c-1.125,2.406-2.313,4.781-3.609,7.094c-4.547,8.078-10.141,15.703-17,22.563
C212.475,453.934,186.397,464.73,158.647,464.73z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_z9k8{transform-origin:center;animation:spinner_StKS .75s infinite linear}@keyframes spinner_StKS{100%{transform:rotate(360deg)}}</style><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_z9k8"/></svg>

After

Width:  |  Height:  |  Size: 538 B

1
fe/src/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,118 @@
import { useState } from "react";
import { useLocation, Link } from "wouter";
import {
Alert,
Button,
TextField,
Typography,
Box,
Container,
Card,
CardHeader,
CardContent,
Stack,
CircularProgress,
} from "@mui/material";
import useStore from "../logic/store";
import { AppBunt, Site } from "../logic/types";
import { enkebab } from "../logic/utils";
import { Centered, Flex, WholeFlex } from "../ui/Components";
export default function () {
const { sync, dashIO } = useStore(["sync", "dashIO"]);
const [location, navigate] = useLocation();
const [loading, setLoading] = useState(false);
const [siteName, setSite] = useState("");
const [sitePath, setPath] = useState("/");
const [siteDesc, setDesc] = useState("");
const [error, setError] = useState("");
function setSitePath(e: React.ChangeEvent<HTMLInputElement>) {
const input = e.currentTarget.value.slice(1).replace("/", "");
setPath("/" + input);
}
// TODO add automated name change logic if site already exists
async function create() {
const { createGroup, createSite } = dashIO();
if (!siteName) {
setError("Site name can't be empty");
return;
}
setLoading(true);
// Create Group first
// Then create site on agent
const site: Site = {
sitename: siteName,
description: siteDesc,
groupname: enkebab(siteName),
binding: sitePath,
icon: "",
home: "",
css: "",
apps: AppBunt,
"app-order": [],
hidden: false,
};
const res = await createSite(site);
if (res) {
await createGroup(siteName);
await sync();
navigate("/");
console.log(res, "res");
}
}
return (
<WholeFlex sx={{ overflowY: "auto", height: "100vh" }}>
<Container>
<Card sx={{ mt: 4 }}>
<CardHeader title="Create a New Site" />
<CardContent>
<Typography variant="body1">
Choose the name of your site and the URL path you want for it
</Typography>
{error && <Alert severity="error">{error}.</Alert>}
<Stack direction="column" useFlexGap sx={{ my: 5, gap: "2rem" }}>
<TextField
label="Site Name"
variant="standard"
fullWidth
value={siteName}
onChange={(e) => setSite(e.currentTarget.value)}
/>
<TextField
label="Site Path"
variant="standard"
fullWidth
value={sitePath}
onChange={setSitePath}
/>
<TextField
label="Description"
variant="standard"
fullWidth
multiline
value={siteDesc}
onChange={(e) => setDesc(e.currentTarget.value)}
/>
</Stack>
{loading ? (
<Centered>
<CircularProgress />
</Centered>
) : (
<>
<Button variant="contained" onClick={create}>
Create
</Button>
<Link to="/">
<Button variant="text">Cancel</Button>
</Link>
</>
)}
</CardContent>
</Card>
</Container>
</WholeFlex>
);
}

View File

@ -0,0 +1,87 @@
import useStore from "../logic/store";
import { Site } from "../logic/types";
import { useLocation, Link as RouterLink } from "wouter";
import {
Card,
CardContent,
CardMedia,
Paper,
Typography,
Button,
Container,
Stack,
} from "@mui/material";
import { DEFAULT_ICON } from "../logic/constants";
import { Centered, Row, SpreadRow } from "../ui/Components";
import * as muiStyles from "@mui/material/styles";
import { ThemeToggle } from "../ui/theme";
// import { ColorToggle } from "../ui/theme";
export default function () {
const using = muiStyles.useColorScheme();
const [_location, navigate] = useLocation();
const { state } = useStore(["state"]);
const list = Object.values(state);
function create() {
navigate("/create-site");
}
return (
<Container maxWidth="md" sx={{ p: 5 }}>
<SpreadRow>
<Centered>
<Typography variant="h2" align="center" sx={{ mb: 4 }}>
Urbit Community Manager
</Typography>
</Centered>
<ThemeToggle />
</SpreadRow>
<Typography align="center" variant="h3">
Your Sites
</Typography>
<Stack
direction={"column"}
useFlexGap
sx={{ my: 2, gap: 2, minHeight: "30vw" }}
>
{list.length !== 0 ? (
list.map((site) => <SiteCard key={site.groupname} site={site} />)
) : (
<Typography variant="h5" sx={{ mt: 5 }} align="center">
Nothing yet{" "}
</Typography>
)}
</Stack>
<Centered sx={{ m: 2 }}>
<Button variant="contained" onClick={create}>
Create Site
</Button>
</Centered>
</Container>
);
}
function SiteCard({ site }: { site: Site }) {
console.log(site, "site");
const icon = site.icon || DEFAULT_ICON;
// Responsive font sizes huh
return (
<RouterLink to={`/admin${site.binding}`}>
<Paper elevation={8}>
<Card variant="outlined">
<CardMedia src={icon} />
<CardContent>
<Stack direction="row" useFlexGap sx={{ gap: "2rem" }}>
<img
style={{ width: 40, height: 40, border: "1px solid primary" }}
src={site.icon || DEFAULT_ICON}
/>
<Typography sx={{ typography: { sm: "h4", xs: "body1" } }}>
{site.sitename}
</Typography>
</Stack>
</CardContent>
</Card>
</Paper>
</RouterLink>
);
}

543
fe/src/dashboard/Site.tsx Normal file
View File

@ -0,0 +1,543 @@
import { useEffect, useState } from "react";
import {
AppType,
Apps,
Channels,
ChannelsBunt,
RichMetadata,
Ship,
Site,
Wiki,
} from "../logic/types";
import {
Button,
TextField,
Typography,
Box,
Container,
CardHeader,
Card,
CardContent,
CardActions,
CircularProgress,
Avatar,
Collapse,
Accordion,
AccordionSummary,
AccordionDetails,
Link as MuiLink,
List,
ListItem,
Divider,
} from "@mui/material";
import useStore from "../logic/store";
import {
AddAppModal,
ConfirmationModal,
StaticPageModal,
} from "../modals/Modal";
import { ChannelType, Group } from "../logic/types-tlon";
import { APP_NAME, RADIO_SHIP, WIKI_SHIP } from "../logic/constants";
import { useLocation } from "wouter";
import { capitalize, doubleCheckApps, delay, enkebab } from "../logic/utils";
import DragContainer from "../ui/Drag.tsx";
import { DropResult } from "@hello-pangea/dnd";
import { Centered, SpreadRow } from "../ui/Components.tsx";
import { ExpandMore } from "@mui/icons-material";
export function SiteDash({ site, group }: { site: Site; group: Group }) {
useEffect(() => {
setChans();
// const apporder = ["blog", "chat", "forum", "radio", "wiki", "about"];
const order = [...site["app-order"]];
if (!order.includes("blog")) order.push("blog");
if (!order.includes("chat")) order.push("chat");
if (!order.includes("forum")) order.push("forum");
if (!order.includes("radio")) order.push("radio");
if (!order.includes("wiki")) order.push("wiki");
for (const title of Object.keys(site.apps.static)) {
const key = `static/${title}` as AppType;
if (!order.includes(key)) order.push(key);
}
setAppOrder(order as AppType[]);
}, [group, site]);
function setChans() {
const chans = Object.entries(group.channels);
const agg = chans.reduce((acc: Channels, [nam, chan]) => {
const [kind, ship, name] = nam.split("/");
const host = ship as Ship;
const meta: RichMetadata = {
...chan.meta,
name,
host,
kind: kind as ChannelType,
};
if (kind === "chat") return { ...acc, chats: [...acc.chats, meta] };
else if (kind === "diary" && name.includes("ucm-ublog"))
return { ...acc, blog: meta };
else if (kind === "diary")
return { ...acc, forums: [...acc.forums, meta] };
else return acc;
}, ChannelsBunt);
setChannels(agg);
}
useEffect(() => {
fetchWikis();
}, []);
async function fetchWikis() {
try {
const res = await dashIO().scryWiki();
setWiki(res.filter((w) => w.public));
} catch {
setWiki(null);
}
}
const { setModal, dashIO, pikes, sync } = useStore([
"setModal",
"dashIO",
"pikes",
"sync",
]);
const [wikis, setWiki] = useState<Wiki[] | null>(null);
const [modal, showModal] = useState(false);
const [error, setError] = useState("");
const [homeMarkdown, setHome] = useState(site.home);
const [customPalette, setPalette] = useState(site.css);
const [siteName, setSite] = useState(site.sitename);
const [siteDesc, setDesc] = useState(site.description);
const [sitePath, setPath] = useState(site.binding);
const [siteIcon, setSiteIcon] = useState(site.icon);
const [hidden, setHidden] = useState(site.hidden);
const [channels, setChannels] = useState(ChannelsBunt);
const [appOrder, setAppOrder] = useState<AppType[]>([]);
const [apps, setApps] = useState<Apps>(site.apps);
useEffect(() => {
doubleCheckApps(site.apps, channels, pikes).then((a) => setApps(a));
}, [channels, site, pikes]);
function setSitePath(e: React.ChangeEvent<HTMLInputElement>) {
const input = e.currentTarget.value.slice(1).replace("/", "");
setPath("/" + input);
}
async function save() {
const ns: Site = {
description: siteDesc,
icon: siteIcon,
home: homeMarkdown,
css: customPalette,
binding: sitePath,
sitename: siteName,
groupname: enkebab(siteName),
"app-order": appOrder,
apps,
hidden,
};
const res = await dashIO().createSite(ns);
sync();
}
// drag
function reorder(result: DropResult<string>) {
// dropped outside the list
if (!result.destination) {
return;
}
const newlist = Array.from(appOrder);
const [removed] = newlist.splice(result.source.index, 1);
newlist.splice(result.destination.index, 0, removed);
setAppOrder(newlist);
}
const [loading, setLoading] = useState(false);
const [_, navigate] = useLocation();
async function destroy() {
const res = await dashIO().delSite(site.binding);
if (res) {
await sync();
navigate("/");
}
}
function confirmDestroy() {
setModal(<ConfirmationModal fun={destroy} text="Destroy" color="error" />);
}
return (
<Box sx={{ overflowY: "auto", height: "100%" }}>
<Container maxWidth="lg">
<SpreadRow sx={{ my: 3 }}>
{loading ? (
<CircularProgress />
) : (
<Button
onClick={confirmDestroy}
color="error"
sx={{ width: "max-content" }}
variant="contained"
>
Destroy
</Button>
)}
<Centered>
<Typography variant="h3" align="center">
{site.sitename}
</Typography>
</Centered>
<a href={site.binding}>
<Button variant="contained">Open</Button>
</a>
</SpreadRow>
<Box
m={3}
sx={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<TextField
label="Site Name"
variant="standard"
fullWidth
value={siteName}
onChange={(e) => setSite(e.currentTarget.value)}
/>
<TextField
label="Site Path"
variant="standard"
fullWidth
value={sitePath}
onChange={setSitePath}
/>
<TextField
label="Description"
variant="standard"
fullWidth
multiline
value={siteDesc}
onChange={(e) => setDesc(e.currentTarget.value)}
/>
<Box sx={{ display: "flex", gap: "2rem" }}>
<Avatar src={siteIcon} variant="square" />
<TextField
label="Site Icon"
variant="standard"
fullWidth
value={siteIcon}
onChange={(e) => setSiteIcon(e.currentTarget.value)}
/>
</Box>
</Box>
<Box>
<Typography align="center" variant="h5">
Apps
</Typography>
<div
style={{ height: "20rem", marginTop: "2rem", marginBottom: "2rem" }}
className="dnd-wrapper"
>
<DragContainer
apps={[...appOrder, "static/"]}
buildCard={cardWrapper({ ...site, apps }, channels, wikis)}
onEnd={reorder}
/>
</div>
</Box>
<Accordion slotProps={{ transition: { unmountOnExit: true } }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Home Page</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography align="center" variant="body1">
Write some Markdown to display in the root page of your site
</Typography>
<TextField
sx={{ mt: 3, height: "400px" }}
slotProps={{
input: { style: { height: "400px" } },
htmlInput: { style: { height: "400px" } },
}}
multiline
fullWidth
value={homeMarkdown}
onChange={(e) => setHome(e.currentTarget.value)}
/>
</AccordionDetails>
</Accordion>
{/*<Accordion slotProps={{ transition: { unmountOnExit: true } }}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Custom Styling</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
{APP_NAME} uses{" "}
<MuiLink
sx={{ mr: "0.5ch" }}
target="_blank"
href="https://mui.com/material-ui/customization/palette/"
>
Material UI
</MuiLink>
for its styling
</Typography>
<Typography>
You can use a custom color palette by adding the palette object in
JSON format below.
</Typography>
<TextField
sx={{ mt: 3 }}
multiline
fullWidth
rows={40}
value={customPalette}
onChange={(e) => setPalette(e.currentTarget.value)}
/>
</AccordionDetails>
</Accordion>*/}
<Centered>
<Button sx={{ my: 4 }} variant="contained" onClick={save}>
Save Changes
</Button>
</Centered>
</Container>
</Box>
);
}
function cardWrapper(site: Site, channels: Channels, wiki: Wiki[] | null) {
return function buildCard(a: AppType) {
const { enabled, content, actions } =
a === "blog"
? buildBlogCard(site, channels)
: a === "chat"
? buildChatCard(site)
: a === "forum"
? buildForumCard(site)
: a === "radio"
? buildRadioCard(site)
: a === "wiki"
? buildWikiCard(site, wiki)
: buildStaticCard(a, site);
const opacity = enabled ? 1 : 0.7;
const title = a.startsWith("static") ? "Static" : capitalize(a);
return (
<Card
sx={{
borderRadius: 0,
textAlign: "center",
height: "100%",
overflow: "hidden",
width: "8rem",
opacity,
display: "flex",
flexDirection: "column",
padding: "0.35rem",
gap: 0,
}}
>
<CardHeader title={title} />
<Divider />
<CardContent
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
flexGrow: 1,
overflowY: "auto",
overflowX: "hidden",
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
display: "none",
},
}}
>
{content}
</CardContent>
<Divider />
<CardActions
sx={{
justifyContent: "center",
margin: "0 auto",
height: "1.8rem",
}}
>
{actions}
</CardActions>
</Card>
);
};
}
function buildBlogCard(site: Site, channels: Channels) {
const { setModal, dashIO, sync } = useStore(["setModal", "dashIO", "sync"]);
const [loading, setLoading] = useState(false);
function addBlog() {
setModal(<AddAppModal kind="blog" site={site} />);
}
async function toggle(blog: string) {
setLoading(true);
const ns = { ...site, apps: { ...site.apps, blog } };
const res = await dashIO().createSite(ns);
await sync();
setLoading(false);
}
const enabled = !!site.apps.blog;
const content = <Typography>{channels.blog?.title}</Typography>;
const actions = loading ? (
<CircularProgress />
) : !channels.blog ? (
<Button onClick={addBlog}>Create</Button>
) : !enabled ? (
<Button onClick={() => toggle(channels.blog?.name!)}>Enable</Button>
) : (
<Button onClick={() => toggle("")}>Disable</Button>
);
return { enabled, content, actions };
}
function buildChatCard(site: Site) {
const { setModal } = useStore(["setModal"]);
function addChat() {
setModal(<AddAppModal kind="chat" site={site} />);
}
const enabled = site.apps.chat.length > 0;
const content = !enabled ? (
<Typography>No chats</Typography>
) : (
<List sx={{ height: "100%" }} disablePadding>
{site.apps.chat.map((c, i) => (
<ListItem key={c + i} sx={{ p: 0 }}>
<Box>-{c}</Box>
</ListItem>
))}
</List>
);
const actions = <Button onClick={addChat}>Add</Button>;
return { enabled, content, actions };
}
function buildForumCard(site: Site) {
const { setModal } = useStore(["setModal"]);
function addForum() {
setModal(<AddAppModal kind="forum" site={site} />);
}
const enabled = site.apps.forum.length > 0;
const content = !enabled ? (
<Typography>No forums</Typography>
) : (
<List sx={{ height: "100%" }} disablePadding>
{site.apps.forum.map((c, i) => (
<ListItem key={c + i} sx={{ p: 0 }}>
<Box>-{c}</Box>
</ListItem>
))}
</List>
);
const actions = <Button onClick={addForum}>Add</Button>;
return { enabled, content, actions };
}
function buildRadioCard(site: Site) {
const { sync, dashIO, pikes } = useStore(["sync", "dashIO", "pikes"]);
const [loading, setLoading] = useState(false);
const { createSite, scryPikes } = dashIO();
async function toggle(bool: boolean) {
setLoading(true);
const ns = { ...site, apps: { ...site.apps, radio: bool } };
await createSite(ns);
await sync();
setLoading(false);
}
async function install() {
setLoading(true);
const res = await dashIO().installApp(RADIO_SHIP, "radio");
await delay(3000);
const pikes = await scryPikes();
if (pikes?.radio?.sync?.ship === RADIO_SHIP) setLoading(false);
else {
await delay(5000);
if (pikes?.radio?.sync?.ship === RADIO_SHIP) setLoading(false);
}
// sync();
}
const pike = pikes.radio;
const installed = pike?.sync?.ship === RADIO_SHIP;
const content = !installed ? <Typography>Not Installed</Typography> : <></>;
const enabled = site.apps.radio;
const actions = loading ? (
<CircularProgress />
) : !installed ? (
<Button onClick={install}>Install</Button>
) : site.apps.radio ? (
<Button onClick={() => toggle(false)}>Disable</Button>
) : (
<Button onClick={() => toggle(true)}>Enable</Button>
);
return { enabled, content, actions };
}
function buildWikiCard(site: Site, wiki: Wiki[] | null) {
const { sync, dashIO, pikes } = useStore(["sync", "dashIO", "pikes"]);
const [loading, setLoading] = useState(false);
async function disable() {
const ns = { ...site, apps: { ...site.apps, wiki: "" } };
const res = await dashIO().createSite(ns);
sync();
}
async function toggleWiki(name: string) {
const ns = { ...site, apps: { ...site.apps, wiki: name } };
const res = await dashIO().createSite(ns);
sync();
}
async function install() {
setLoading(true);
const res = await dashIO().installApp(WIKI_SHIP, "wiki");
sync();
}
const pike = pikes.wiki;
const installed = pike?.sync?.ship === WIKI_SHIP;
const content = !wiki ? (
<Typography>Not Installed</Typography>
) : (
wiki.map((w) => (
<Typography
sx={{
cursor: "pointer",
border: site.apps.wiki === w.id ? "1px solid black" : "none",
}}
onClick={() => toggleWiki(w.id)}
key={w.id}
>
{w.title}
</Typography>
))
);
const actions = !wiki ? (
<Button onClick={install}>Install</Button>
) : site.apps.wiki ? (
<Button onClick={disable}>Disable</Button>
) : null;
return { enabled: installed, content, actions };
}
function buildStaticCard(title: string, site: Site) {
const { setModal } = useStore(["setModal"]);
async function openEditor() {
const text = site.apps.static[name];
setModal(<StaticPageModal name={name} text={text} site={site} />);
}
const [_, name] = title.split("/");
if (name) {
const content = <Typography>{name}</Typography>;
const actions = <Button onClick={openEditor}>Edit</Button>;
return { enabled: true, content, actions };
} else {
const content = <></>;
const actions = <Button onClick={openEditor}>Add</Button>;
return { enabled: false, content, actions };
}
}
export default SiteDash;

549
fe/src/logic/comms.ts Normal file
View File

@ -0,0 +1,549 @@
import anyAscii from "any-ascii";
import {
faker,
Faker,
es,
en,
zh_CN,
ja,
th,
mergeLocales,
} from "@faker-js/faker";
import { APP_NAME, RADIO_SHIP, WIKI_SHIP } from "./constants";
import {
AppChoice,
AppType,
Pikes,
RichMetadata,
Ship,
Site,
Wiki,
} from "./types";
import { enkebab, generateRandomBase64, isValidHttpUrl } from "./utils";
import {
ChannelsRes,
Content,
DiaryPage,
DiaryPost,
Essay,
PostsPage,
} from "./types-tlon";
import Urbit from "@urbit/http-api";
import { hex2patp } from "./ob/co";
import { tokenize } from "./tlon-helpers";
type EventHandler = (data: any) => void;
type ErrorHandler = (data: any, id: string) => void;
export function urbitIO(airlock: Urbit) {
const sub = async (
app: string,
path: string,
event: EventHandler,
err?: ErrorHandler,
quit?: EventHandler,
) => airlock.subscribe({ app, path, event });
const unsub = async (num: number) => airlock.unsubscribe(num);
const scry = async (app: string, path: string) => airlock.scry({ app, path });
const poke = async (app: string, mark: string, json: any) =>
airlock.poke({ app, mark, json });
const thread = async (
threadName: string,
desk: string,
inputMark: string,
outputMark: string,
body: any,
) => airlock.thread({ threadName, desk, inputMark, outputMark, body });
return {
sub,
unsub,
scry,
poke,
thread,
};
}
export function dashIO(airlock: Urbit) {
const { scry, sub, unsub, poke, thread } = urbitIO(airlock);
async function initSubs(handler: EventHandler) {
const err = (err: any, id: string) => console.log(err, "");
const quit = (data: any) => console.log(data, "");
const res = await sub(APP_NAME, "/ui", handler, err, quit);
return res;
}
async function initialScries() {
const groups = scry("groups", "/groups/v1");
const pikes = scry("hood", "/kiln/pikes");
return { groups: await groups, pikes: await pikes };
}
// pokes
async function createSite(site: Site) {
const obj = { dash: { set: site } };
return poke(APP_NAME, "json", obj);
}
async function delSite(path: string) {
const obj = { dash: { del: path } };
return poke(APP_NAME, "json", obj);
}
// Tlon
async function createGroup(title: string) {
const name = enkebab(title);
const mark = "group-create";
const app = "groups";
const json = {
title,
description: "Backend Group for UCM site",
image: "#999999",
cover: "#D9D9D9",
name,
members: {},
cordon: {
open: {
ships: [],
ranks: [],
},
},
secret: false,
};
return poke(app, mark, json);
}
async function createBlog(
groupname: string,
title: string,
description: string,
) {
const group = `~${airlock.ship}/${groupname}`;
const randomString = generateRandomBase64(3);
const name = "ucm-" + "ublog-" + enkebab(title) + "-" + randomString;
const mark = "channel-action";
const app = "channels";
const json = {
create: {
kind: "diary",
group,
name,
title,
description,
readers: [],
writers: [],
},
};
console.log(json);
return poke(app, mark, json);
}
async function createChannel(
groupname: string,
title: string,
description: string,
kind: string,
) {
const group = `~${airlock.ship}/${groupname}`;
const randomString = generateRandomBase64(3);
const name = "ucm-" + enkebab(title) + "-" + randomString;
const mark = "channel-action";
const app = "channels";
const json = {
create: {
kind,
group,
name,
title,
description,
readers: [],
writers: [],
},
};
// console.log(json);
return poke(app, mark, json);
}
async function scryState(): Promise<any> {
const res = await scry(APP_NAME, "/state");
return res;
}
async function scryGroups() {
const app = "groups";
const path = "/groups/v1";
return await scry(app, path);
}
async function scryPikes() {
const app = "hood";
const path = "/kiln/pikes";
return await scry(app, path);
}
async function fetchApps(pikes: Pikes) {
const haveWiki = pikes.wiki && pikes.wiki.sync.ship === WIKI_SHIP;
if (!haveWiki) installApp(WIKI_SHIP, "wiki");
}
async function installApp(ship: Ship, appName: string) {
const app = "docket";
const mark = "docket-install";
const json = `${ship}/${appName}`;
return poke(app, mark, json);
}
async function scryWiki(): Promise<Wiki[]> {
const app = "wiki";
const path = `/list/mine`;
return await scry(app, path);
}
return {
initSubs,
initialScries,
createSite,
delSite,
createGroup,
createBlog,
createChannel,
scryGroups,
scryState,
scryPikes,
fetchApps,
installApp,
scryWiki,
};
}
export default function appIO(airlock: Urbit) {
const { scry, sub, unsub, poke, thread } = urbitIO(airlock);
async function channelsSub(handler: EventHandler) {
const err = (err: any, id: string) => console.log(err, "");
const quit = (data: any) => console.log(data, "");
const res = await sub("channels", "/v1", handler, err, quit);
return res;
}
// {
// "kind": "chat",
// "group": "~zod/my-test-site-3",
// "name": "shat-number-2",
// "title": "shat number 2",
// "description": "my new shat",
// "readers": [],
// "writers": []
// }
// tlom
async function sendChatMsg(
author: Ship,
host: Ship,
name: string,
content: Content,
sent: number,
) {
const nest = `chat/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
add: {
"kind-data": {
chat: null,
},
author,
content,
sent,
},
},
},
},
};
return poke(app, mark, json);
}
async function sendChatReply(
author: Ship,
host: Ship,
name: string,
parent: string,
content: Content,
) {
const nest = `chat/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
reply: {
id: parent,
action: {
add: {
author,
sent: Date.now(),
content,
},
},
},
},
},
},
};
return poke(app, mark, json);
}
async function sendDiaryPost(
author: Ship,
host: Ship,
name: string,
title: string,
image: string,
content: Content,
sent: number,
) {
const nest = `diary/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const kind = { diary: { title, image } };
const json = {
channel: {
nest,
action: {
post: {
add: {
author,
sent,
content,
"kind-data": kind,
},
},
},
},
};
return poke(app, mark, json);
}
async function sendDiaryReply(
author: Ship,
host: Ship,
name: string,
parent: string,
content: Content,
) {
const nest = `diary/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
reply: {
id: parent, // dotted!
action: {
add: {
author,
sent: Date.now(),
content,
},
},
},
},
},
},
};
console.log(json, "sending diary reply");
return poke(app, mark, json);
}
async function sendReact(
author: Ship,
kind: string,
host: Ship,
name: string,
id: string,
react: string,
) {
const nest = `${kind}/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
"add-react": {
id,
react,
ship: author,
},
},
},
},
};
return poke(app, mark, json);
}
async function sendReplyReact(
author: Ship,
kind: string,
host: Ship,
name: string,
parentId: string,
id: string,
react: string,
) {
const nest = `${kind}/${host}/${name}`;
const mark = "channel-action";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
reply: {
id: parentId,
action: {
"add-react": {
id,
react,
ship: author,
},
},
},
},
},
},
};
return poke(app, mark, json);
}
async function scryDiary(
ship: Ship,
name: string,
count: number,
): Promise<DiaryPage> {
const app = "channels";
const path = `/v1/diary/${ship}/${name}/posts/newest/${count}/post`;
return await scry(app, path);
}
async function scryDiaryPost(
ship: Ship,
name: string,
id: string,
): Promise<DiaryPost> {
const app = "channels";
const path = `/v1/diary/${ship}/${name}/posts/post/${id}`;
return await scry(app, path);
}
async function scryChat(
ship: Ship,
name: string,
count: number,
): Promise<PostsPage> {
const app = "channels";
const path = `/v1/chat/${ship}/${name}/posts/newest/${count}/post`;
return await scry(app, path);
}
// other apps
async function search(
query: string,
apps: AppType[],
by: string,
start?: number,
end?: number,
) {
const obj = { site: { search: { query, apps, by, start, end } } };
return poke(APP_NAME, "json", obj);
}
async function scryChatroom(
ship: Ship,
name: string,
count: number,
): Promise<PostsPage> {
const res = await scry(
"channels",
`/v2/chat/${ship}/${name}/posts/newest/${count}/outline`,
);
return res;
}
async function fixChatMsg(
airlock: Urbit,
host: Ship,
name: string,
id: string,
essay: Essay,
) {
const nest = `chat/${host}/${name}`;
const mark = "channel-action";
const desk = "groups";
const app = "channels";
const json = {
channel: {
nest,
action: {
post: {
edit: {
id,
essay,
},
},
},
},
};
console.log(airlock, "airlock");
return airlock.ninjaPoke({ desk, app, mark, json, proxyApp: "ucm" });
}
async function scryChannels(): Promise<ChannelsRes> {
const res = await scry("channels", "/v2/channels/full");
// const res4 = await scry("channels", "/v2/heads");
// console.log(res4, "channs4");
return res;
}
async function radioSub(handler: EventHandler) {
return sub("tenna", "/frontend", handler);
}
async function radioSub2(handler: EventHandler) {
await sub("tower", "/greg/local", handler);
// return sub("tower", "/personal", handler);
}
async function radioChat(from: Ship, message: string) {
const time = Date.now();
const json = {
chat: {
from,
message,
time,
},
};
return poke("tenna", "radio-action", json);
}
async function radioSpin(playUrl: string) {
if (!isValidHttpUrl(playUrl)) return;
let currentUnixTime = Date.now();
currentUnixTime = Math.ceil(currentUnixTime);
const json = {
spin: {
url: playUrl,
time: currentUnixTime,
},
};
return await poke("tenna", "radio-action", json);
}
return {
channelsSub,
sendChatMsg,
sendChatReply,
sendDiaryPost,
sendDiaryReply,
sendReact,
sendReplyReact,
scryDiary,
scryDiaryPost,
scryChat,
search,
scryChatroom,
fixChatMsg,
scryChannels,
radioSub,
radioSub2,
radioSpin,
radioChat,
};
}

37
fe/src/logic/constants.ts Normal file
View File

@ -0,0 +1,37 @@
import { Ship } from "./types";
export const APP_NAME = "ucm";
export const URL = import.meta.env.PROD ? "" : "http://localhost:8081";
export const MOBILE_BROWSER_REGEX =
/Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
export const AUDIO_REGEX = new RegExp(/https:\/\/.+\.(mp3|wav|ogg)\b/gim);
export const VIDEO_REGEX = new RegExp(/https:\/\/.+\.(mov|mp4|ogv)\b/gim);
export const TWITTER_REGEX = new RegExp(
/https:\/\/(twitter|x)\.com\/.+\/status\/\d+/gim,
);
export const REF_REGEX = new RegExp(
/urbit:\/\/[a-z0-9-]+\/~[a-z-_]+\/[a-z0-9-_]+/gim,
);
export const RADIO_REGEX = new RegExp(/urbit:\/\/radio\/~[a-z-_]+/gim);
export const IMAGE_REGEX = new RegExp(
/https:\/\/.+\.(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)\b/gim,
);
export const SHIP_REGEX = new RegExp(/\B~[a-z-]+/);
export const HASHTAGS_REGEX = new RegExp(/#[a-z-]+/g);
export const DEFAULT_DATE = { year: 1970, month: 1, day: 1 };
export const RADIO = "📻";
export const RADIO_SHIP: Ship = "~nodmyn-dosrux" as Ship;
export const WIKI_SHIP: Ship = "~holnes" as Ship;
export const DEFAULT_ICON = "https://s3.sortug.com/img/icons/ucm-logo.png";
export const MAGIC_COMET =
"~dozzod-polwex-dozzod-polwex--dozzod-polwex-dozzod-polwex";
const paths = window.location.pathname.split("/");
export const BASE_PATH = `/${paths[4]}`;

Some files were not shown because too many files have changed in this diff Show More